diff --git a/backend/PRACTICA_01_BACKEND/.idea/.gitignore b/backend/PRACTICA_01_BACKEND/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/backend/PRACTICA_01_BACKEND/.idea/PRACTICA_01_BACKEND.iml b/backend/PRACTICA_01_BACKEND/.idea/PRACTICA_01_BACKEND.iml new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/.idea/PRACTICA_01_BACKEND.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/.idea/misc.xml b/backend/PRACTICA_01_BACKEND/.idea/misc.xml new file mode 100644 index 000000000..31e1ebce6 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/.idea/modules.xml b/backend/PRACTICA_01_BACKEND/.idea/modules.xml new file mode 100644 index 000000000..f9f0ad2a7 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/.idea/vcs.xml b/backend/PRACTICA_01_BACKEND/.idea/vcs.xml new file mode 100644 index 000000000..b2bdec2d7 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/Creacion ben User.md b/backend/PRACTICA_01_BACKEND/Creacion ben User.md new file mode 100644 index 000000000..95a33ca38 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/Creacion ben User.md @@ -0,0 +1,87 @@ +### Creacion ben UserClient + +Creacion de las variables el constructor y geter y seter la clase UserClient tendra la anotacion @Entity y la anotacion @Id para identificarlo en la base de datos + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-37-47-image.png) + +### Creacion del User Service + +creare el User service a partir del service de words en la clase UserService le pondre la anotacion @Service para identificarlo como Service e injectare la clase UserRepositori para hacer las operaciones con base de datos con la anotacion @Autowired aqui creare los metodos crear, borrar,actualizar y consulta a la bas de datos haciendo en cada metodo la llamada al UserRepository + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-35-23-image.png) + +### Creacion de la interface user Repository + +En esta interface haremos un extends de JpaRepository + +hi hare una llamada al metodo getUserById pasandole por parametro el id del Usuario + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-34-09-image.png) + +### Creacion del User Conroller + +Al crear el UserController le pondre las anotaciones @RestController +@RequestMapping para identificar que es un controlador lueho injectare el UserrService Para acceder a los metodos de la clase + ++ Creare los metodos getAll users, getUserById, CreateUser, UpdateUser, DeleteUser con las anotaciones @GetMapping,para el metodo GetAllUsers + ++ @GetMapping("/{id}") para filtrar por id + ++ @PostMapping("/createUser") para crear un usuario + ++ @PutMapping("/{id}") para actualizar el Usuario por id + ++ @DeleteMapping("/{id}") para borrar el Usuario por id + ++ Añadire el metodo que nos pado el maestro para añadir las cabeceras que dan informmacion de consumo de memotia del procesador y el entorno o jdk que estamos utilizando + +##### Tambien utilizare las anotaciones + ++ @PathVariable String id para recoger los usuarios por id + ++ @RequestBody UserClient User es para acceder a las propiedades de el User client + ++ Utilizare el metodo ResponseEntity para hacer la peticion http + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-39-00-image.png) + +### Creacion del healt controller + +Esta clase nos la enseño el maetro hi la implemente, esta clase a trabes de @RestController +@RequestMapping("/api/health") accede a a el sevice hi introduce las cabeceras donde viene la informacion de consumo de recursos y version del jdk + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-40-06-image.png) + +### Ejecucion del proyecto + +Ejecuto el proyecto sin problemas + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-41-06-image.png) + +### Creacion de Bse de datos + +El Springboot me crea la base de datos + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-42-00-image.png) + +### Creacion del moc + +creo el moc con el metodo put para introducoir datos de usuario + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-43-37-image.png) + +### Setear Usuario desde postman + +el moc no me da error + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-49-01-image.png) + +### Introducion en la base de datos + +he creado el primer usuario + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-09-49-55-image.png) + +### Clase Test + +la clase test no la he implementado por que aun no he echo pruebas uniotarias diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/.gitignore b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/ProyectoBackend.iml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/ProyectoBackend.iml new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/ProyectoBackend.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/compiler.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/compiler.xml new file mode 100644 index 000000000..1dd658864 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/encodings.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/encodings.xml new file mode 100644 index 000000000..3e004b894 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/jarRepositories.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/jarRepositories.xml new file mode 100644 index 000000000..712ab9d98 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/misc.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/misc.xml new file mode 100644 index 000000000..5c7926759 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/modules.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/modules.xml new file mode 100644 index 000000000..c1d78309e --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitattributes b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitattributes new file mode 100644 index 000000000..3b41682ac --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitignore b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.mvn/wrapper/maven-wrapper.properties b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..d58dfb70b --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw new file mode 100755 index 000000000..19529ddf8 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw.cmd b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw.cmd new file mode 100644 index 000000000..906b218a5 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - userClient and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/pom.xml b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/pom.xml new file mode 100644 index 000000000..68220ba2e --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.7 + + + dev + pronunciationAppBack + 0.0.1-SNAPSHOT + pronunciationAppBack + Spring Boot for app pronunciatoin + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.h2database + h2 + runtime + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/PronunciationAppBackApplication.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/PronunciationAppBackApplication.java new file mode 100644 index 000000000..97cee09e5 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/PronunciationAppBackApplication.java @@ -0,0 +1,13 @@ +package dev.pronunciationAppBack; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PronunciationAppBackApplication { + + public static void main(String[] args) { + SpringApplication.run(PronunciationAppBackApplication.class, args); + } + +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java new file mode 100644 index 000000000..5af962fdf --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java @@ -0,0 +1,80 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + + @RestController + @RequestMapping("/api/health") + public class HealthController { + + @Autowired + private WordService wordService; + + @GetMapping + public ResponseEntity> healthCheck() { + Map healthStatus = new HashMap<>(); + healthStatus.put("status", "UP"); + healthStatus.put("timestamp", new Date()); + healthStatus.put("wordCount", wordService.getWordCount()); + + // verify database connection + boolean databaseConnection = checkDatabaseConnection(); + healthStatus.put("database", databaseConnection ? "Connected" : "Disconnected"); + + // Add more health checks as needed + healthStatus.put("memoryUsage", getMemoryUsage()); + healthStatus.put("diskSpace", getDiskSpace()); + + HttpHeaders headers = getCommonHeaders("Health check endpoint"); + HttpStatus status = databaseConnection ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; + + return new ResponseEntity<>(healthStatus, headers, status); + } + + private boolean checkDatabaseConnection() { + try { + wordService.getAllWords(); + return true; + } catch (Exception e) { + return false; + } + } + + private Map getMemoryUsage() { + Runtime runtime = Runtime.getRuntime(); + Map memoryInfo = new HashMap<>(); + memoryInfo.put("total", runtime.totalMemory()); + memoryInfo.put("free", runtime.freeMemory()); + memoryInfo.put("used", runtime.totalMemory() - runtime.freeMemory()); + return memoryInfo; + } + + private Map getDiskSpace() { + java.io.File root = new java.io.File("/"); + Map diskInfo = new HashMap<>(); + diskInfo.put("total", root.getTotalSpace()); + diskInfo.put("free", root.getFreeSpace()); + diskInfo.put("usable", root.getUsableSpace()); + return diskInfo; + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + return headers; + } + } \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserControler.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserControler.java new file mode 100644 index 000000000..ce13a5261 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserControler.java @@ -0,0 +1,66 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.UserClient; +import dev.pronunciationAppBack.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/UserClient") +public class UserControler { + + @Autowired + public UserService userService; + + @GetMapping + public List getAliUsers() { + List userClients = userService.getAllUser(); + System.out.println("Number of userClients: " + userClients.size()); + for (UserClient userClient : userClients) { + System.out.println("userClients: " + userClients); + } + return userClients; + } + @GetMapping("/{id}") + public Optional getUserById(@PathVariable String id) { + return userService.getUserById(id); + } + + + @PostMapping("/createUser") + public ResponseEntity createUser(@RequestBody UserClient User) { + UserClient createUser = userService.createUser(User); + HttpHeaders headers = getCommonHeaders("Create a new word"); + + return new ResponseEntity<>(createUser, headers, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public UserClient updateUser(@PathVariable String id, @RequestBody UserClient user) { + return userService.updateUser(user); + } + @DeleteMapping("/{id}") + public String deleteUser(@PathVariable("id") String idToDelete) { + userService.deleteUser(idToDelete); + return "UserClient delete"; + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + headers.add("object", "words"); + return headers; + } + +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java new file mode 100644 index 000000000..3ef622d52 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java @@ -0,0 +1,96 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +// a kind of controller that connects the view with the business logic +// the business logic is at service where the core operations are done +// rest controller just works with rest and JSON +// controller works html and templates, for example: TH +@RestController +@RequestMapping("/api/words") +public class WordController { + // dependency injection: boot creates the object and injects it, + // it also manages all the lifecycle + // the real goal is to connect the controller with the repository + @Autowired + public WordService wordService; + + + @GetMapping + public ResponseEntity> getAllWords() { + List words = wordService.getAllWords(); + HttpHeaders headers = getCommonHeaders("Get all words"); + + return !words.isEmpty() + ? new ResponseEntity<>(words, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable String id) { + Optional word = wordService.getWordById(id); + HttpHeaders headers = getCommonHeaders("Get word by ID"); + + return word.map(value -> new ResponseEntity<>(value, headers, HttpStatus.OK)) + .orElseGet(() -> new ResponseEntity<>(headers, HttpStatus.NOT_FOUND)); + } + + @PostMapping("/createWord") + public ResponseEntity createWord(@RequestBody Word word) { + Word createdWord = wordService.createWord(word); + HttpHeaders headers = getCommonHeaders("Create a new word"); + + return new ResponseEntity<>(createdWord, headers, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord(@PathVariable String id, @RequestBody Word word) { + Word updatedWord = wordService.updateWord(word); + HttpHeaders headers = getCommonHeaders("Update a word"); + + return new ResponseEntity<>(updatedWord, headers, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable("id") String idToDelete) { + HttpHeaders headers = getCommonHeaders("Delete a word"); + + if (wordService.existsById(idToDelete)) { + wordService.deleteWord(idToDelete); + return new ResponseEntity<>("Word deleted", headers, HttpStatus.OK); + } else { + return new ResponseEntity<>("Word not found", headers, HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping + public ResponseEntity deleteAllWords() { + wordService.deleteAllWords(); + HttpHeaders headers = getCommonHeaders("Delete all words"); + return new ResponseEntity<>("All words deleted", headers, HttpStatus.OK); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + headers.add("word-count", String.valueOf(wordService.getWordCount())); + headers.add("object", "words"); + return headers; + } + + +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/UserClient.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/UserClient.java new file mode 100644 index 000000000..1b09b21b9 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/UserClient.java @@ -0,0 +1,58 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + + +@Entity +public class UserClient { + + @Id + private String id; + private String name; + private String age; + private boolean is_Active; + + public UserClient() { + } + + public UserClient(String id, String name, String age, boolean is_Active) { + this.id = id; + this.name = name; + this.age = age; + this.is_Active = is_Active; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAge() { + return age; + } + + public void setAge(String age) { + this.age = age; + } + + public boolean isIs_Active() { + return is_Active; + } + + public void setIs_Active(boolean is_Active) { + this.is_Active = is_Active; + } +} + diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java new file mode 100644 index 000000000..10b32e6d7 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java @@ -0,0 +1,100 @@ +package dev.pronunciationAppBack.model; + + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + + +@Entity +public class Word { + + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + public Word() {} + + public Word(String id, String wordName, String definition, String phoneticSpelling, String sentence, boolean isActive, int level) { + this.id = id; + this.wordName = wordName; + this.definition = definition; + this.phoneticSpelling = phoneticSpelling; + this.sentence = sentence; + this.isActive = isActive; + this.level = level; + } + + public String getId() { + return id; + } + + public String getWordName() { + return wordName; + } + + public String getDefinition() { + return definition; + } + + public String getPhoneticSpelling() { + return phoneticSpelling; + } + + public String getSentence() { + return sentence; + } + + public boolean isActive() { + return isActive; + } + + public int getLevel() { + return level; + } + + public void setId(String id) { + this.id = id; + } + + public void setWordName(String wordName) { + this.wordName = wordName; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public void setPhoneticSpelling(String phoneticSpelling) { + this.phoneticSpelling = phoneticSpelling; + } + + public void setSentence(String sentence) { + this.sentence = sentence; + } + + public void setActive(boolean active) { + isActive = active; + } + + public void setLevel(int level) { + this.level = level; + } + + @Override + public String toString() { + return "Word{" + + "id='" + id + '\'' + + ", wordName='" + wordName + '\'' + + ", definition='" + definition + '\'' + + ", phoneticSpelling='" + phoneticSpelling + '\'' + + ", sentence='" + sentence + '\'' + + ", isActive=" + isActive + + ", level=" + level + + '}'; + } +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java new file mode 100644 index 000000000..660769d8a --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java @@ -0,0 +1,9 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.UserClient; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository{ + + UserClient getUserById(String id); +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java new file mode 100644 index 000000000..e0d4c25cb --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java @@ -0,0 +1,10 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface WordRepository extends JpaRepository { + Word getWordById(String id); + Word getWordByPhoneticSpelling(String pronunciation); +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java new file mode 100644 index 000000000..6b68eae28 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java @@ -0,0 +1,53 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.UserClient; +import dev.pronunciationAppBack.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + + +@Service +public class UserService { + + + @Autowired + private UserRepository userRepository; + + public List getAllUser() { + List allUsers = userRepository.findAll(); + return allUsers; + } + + + public Optional getUserById(String id) { + return Optional.ofNullable(userRepository.getUserById(id)); + } + + public UserClient createUser(UserClient userClient) { + return userRepository.save(userClient); + } + + public UserClient updateUser(UserClient userClient) { + return userRepository.save(userClient); + } + + public void deleteUser(String id) { + userRepository.deleteById(id); + } + + public void deleteAllUser() { + userRepository.deleteAll(); + } + + public boolean existsUserById(String id) { + return userRepository.existsById(id); + } + + public long getUserCount() { + return userRepository.count(); + } + + +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java new file mode 100644 index 000000000..fba5d3e1b --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java @@ -0,0 +1,53 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Optional getWordById(String id) { + return Optional.ofNullable(wordRepository.getWordById(id)); + } + + public Word createWord(Word word) { + return wordRepository.save(word); + } + + public Word updateWord(Word word) { + return wordRepository.save(word); + } + + public void deleteWord(String id) { + wordRepository.deleteById(id); + } + + public void deleteAllWords() { + wordRepository.deleteAll(); + } + + public boolean existsById(String id) { + return wordRepository.existsById(id); + } + + public long getWordCount() { + return wordRepository.count(); + } + + // Additional business logic can be added here + public Word getWordByPhoneticSpelling(String pronunciation) { + return wordRepository.getWordByPhoneticSpelling(pronunciation); + } +} \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/resources/application.properties b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/resources/application.properties new file mode 100644 index 000000000..3ca0b8edf --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/main/resources/application.properties @@ -0,0 +1,20 @@ +spring.application.name=pronunciationAppBack + +# H2 DATABASE SERVER +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true + +# H2 IN MEMORY +#spring.datasource.url=jdbc:h2:mem:testdb +#spring.datasource.username=sa +#spring.datasource.password= + + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/pcpla/ProyectoBackend/backend/pronunciationDB.db +spring.datasource.username=Pedro +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=create \ No newline at end of file diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java new file mode 100644 index 000000000..b2842b725 --- /dev/null +++ b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java @@ -0,0 +1,65 @@ +package dev.pronunciationAppBack; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class PronunciationAppBackApplicationTests { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private WordRepository wordRepository; + + @Test + public void testCreateWord() { + Word word = new Word("1", "Example", "A thing characteristic of its kind", "ɪɡˈzæmpəl", "This is an example sentence.", true, 1); + // assign the word object to the repository and save to H2 + Word savedWord = wordRepository.save(word); + assertThat(savedWord).isNotNull(); + assertThat(savedWord.getId()).isEqualTo("1"); + } + + @Test + public void testReadWord() { + Word word = new Word("2", "Test", "A procedure to evaluate", "test", "This is a test sentence.", true, 2); + entityManager.persist(word); + + Word foundWord = wordRepository.findById("2").orElse(null); + assertThat(foundWord).isNotNull(); + assertThat(foundWord.getWordName()).isEqualTo("Test"); + } + + @Test + public void testUpdateWord() { + Word word = new Word("3", "Update", "To bring up to date", "ˈʌpdeɪt", "This word will be updated.", true, 3); + entityManager.persist(word); + + Word wordToUpdate = wordRepository.findById("3").orElse(null); + assertThat(wordToUpdate).isNotNull(); + wordToUpdate.setDefinition("To make something more modern or up to date"); + wordRepository.save(wordToUpdate); + + Word updatedWord = wordRepository.findById("3").orElse(null); + assertThat(updatedWord).isNotNull(); + assertThat(updatedWord.getDefinition()).isEqualTo("To make something more modern or up to date"); + } + + @Test + public void testDeleteWord() { + Word word = new Word("4", "Delete", "To remove or erase", "dɪˈliːt", "This word will be deleted.", true, 4); + entityManager.persist(word); + + wordRepository.deleteById("4"); + + Word deletedWord = wordRepository.findById("4").orElse(null); + assertThat(deletedWord).isNull(); + } +} diff --git a/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationDB.db.mv.db b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationDB.db.mv.db new file mode 100644 index 000000000..fcd6f9232 Binary files /dev/null and b/backend/PRACTICA_01_BACKEND/ProyectoBackend/backend/pronunciationDB.db.mv.db differ diff --git a/backend/pronunciationAppBack/pom.xml b/backend/pronunciationAppBack/pom.xml index 5963cf3b7..68220ba2e 100644 --- a/backend/pronunciationAppBack/pom.xml +++ b/backend/pronunciationAppBack/pom.xml @@ -34,6 +34,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.projectlombok + lombok + true + org.springframework.boot spring-boot-starter-web diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java new file mode 100644 index 000000000..27dcc7185 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/HealthController.java @@ -0,0 +1,88 @@ +package dev.pronunciationAppBack.controller; + + +import dev.pronunciationAppBack.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/health") +public class HealthController { + + @Autowired + private WordService wordService; + + @GetMapping + public ResponseEntity> healthCheck() { + Map healthStatus = new HashMap<>(); + healthStatus.put("status", "UP"); + healthStatus.put("timestamp", new Date()); + healthStatus.put("wordCount", wordService.getWordCount()); + + // verify database connection + boolean databaseConnection = checkDatabaseConnection(); + healthStatus.put("database", databaseConnection ? "Connected" : "Disconnected"); + + // Add more health checks as needed + healthStatus.put("memoryUsage", getMemoryUsage()); + healthStatus.put("diskSpace", getDiskSpace()); + + // Add PID, Java version, and other process info + healthStatus.put("pid", ProcessHandle.current().pid()); + healthStatus.put("javaVersion", System.getProperty("java.version")); + healthStatus.put("javaVendor", System.getProperty("java.vendor")); + healthStatus.put("osName", System.getProperty("os.name")); + healthStatus.put("osVersion", System.getProperty("os.version")); + + HttpHeaders headers = getCommonHeaders("Health check endpoint"); + HttpStatus status = databaseConnection ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; + + return new ResponseEntity<>(healthStatus, headers, status); + } + + private boolean checkDatabaseConnection() { + try { + wordService.getAllWords(); + return true; + } catch (Exception e) { + return false; + } + } + + private Map getMemoryUsage() { + Runtime runtime = Runtime.getRuntime(); + Map memoryInfo = new HashMap<>(); + memoryInfo.put("total", runtime.totalMemory()); + memoryInfo.put("free", runtime.freeMemory()); + memoryInfo.put("used", runtime.totalMemory() - runtime.freeMemory()); + return memoryInfo; + } + + private Map getDiskSpace() { + java.io.File root = new java.io.File("/"); + Map diskInfo = new HashMap<>(); + diskInfo.put("total", root.getTotalSpace()); + diskInfo.put("free", root.getFreeSpace()); + diskInfo.put("usable", root.getUsableSpace()); + return diskInfo; + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + return headers; + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordController.java new file mode 100644 index 000000000..1d5a57c1e --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordController.java @@ -0,0 +1,87 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.StageWord; +import dev.pronunciationAppBack.service.StageWordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/stagewords") +public class StageWordController { + + @Autowired + private StageWordService stageWordService; + + @GetMapping + public ResponseEntity> getAllStageWords() { + List stageWords = stageWordService.getAllStageWords(); + HttpHeaders headers = getCommonHeaders("Get all stage words"); + + return !stageWords.isEmpty() + ? new ResponseEntity<>(stageWords, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getStageWordById(@PathVariable String id) { + Optional stageWord = stageWordService.getStageWordById(id); + HttpHeaders headers = getCommonHeaders("Get stage word by ID"); + + return stageWord.map(value -> new ResponseEntity<>(value, headers, HttpStatus.OK)) + .orElseGet(() -> new ResponseEntity<>(headers, HttpStatus.NOT_FOUND)); + } + + @PostMapping("/createStageWord") + public ResponseEntity createStageWord(@RequestBody StageWord stageWord) { + StageWord createdStageWord = stageWordService.createStageWord(stageWord); + HttpHeaders headers = getCommonHeaders("Create a new stage word"); + + return new ResponseEntity<>(createdStageWord, headers, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateStageWord(@PathVariable String id, @RequestBody StageWord stageWord) { + StageWord updatedStageWord = stageWordService.updateStageWord(stageWord); + HttpHeaders headers = getCommonHeaders("Update a stage word"); + + return new ResponseEntity<>(updatedStageWord, headers, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteStageWord(@PathVariable("id") String idToDelete) { + HttpHeaders headers = getCommonHeaders("Delete a stage word"); + + if (stageWordService.existsById(idToDelete)) { + stageWordService.deleteStageWord(idToDelete); + return new ResponseEntity<>("Stage word deleted", headers, HttpStatus.OK); + } else { + return new ResponseEntity<>("Stage word not found", headers, HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping + public ResponseEntity deleteAllStageWords() { + stageWordService.deleteAllStageWords(); + HttpHeaders headers = getCommonHeaders("Delete all stage words"); + return new ResponseEntity<>("All stage words deleted", headers, HttpStatus.OK); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + headers.add("stage-word-count", String.valueOf(stageWordService.getStageWordCount())); + headers.add("object", "stage-words"); + return headers; + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java new file mode 100644 index 000000000..908061ccb --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java @@ -0,0 +1,88 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/words") +public class WordController { + + @Autowired + private WordService wordService; + + @GetMapping + public ResponseEntity> getAllWords() { + List words = wordService.getAllWords(); + HttpHeaders headers = getCommonHeaders("Get all words"); + + return !words.isEmpty() + ? new ResponseEntity<>(words, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable String id) { + Optional word = wordService.getWordById(id); + HttpHeaders headers = getCommonHeaders("Get word by ID"); + + return word.map(value -> new ResponseEntity<>(value, headers, HttpStatus.OK)) + .orElseGet(() -> new ResponseEntity<>(headers, HttpStatus.NOT_FOUND)); + } + + @PostMapping("/createWord") + public ResponseEntity createWord(@RequestBody Word word) { + Word createdWord = wordService.createWord(word); + HttpHeaders headers = getCommonHeaders("Create a new word"); + + return new ResponseEntity<>(createdWord, headers, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord(@PathVariable String id, @RequestBody Word word) { + Word updatedWord = wordService.updateWord(word); + HttpHeaders headers = getCommonHeaders("Update a word"); + + return new ResponseEntity<>(updatedWord, headers, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable("id") String idToDelete) { + HttpHeaders headers = getCommonHeaders("Delete a word"); + + if (wordService.existsById(idToDelete)) { + wordService.deleteWord(idToDelete); + return new ResponseEntity<>("Word deleted", headers, HttpStatus.OK); + } else { + return new ResponseEntity<>("Word not found", headers, HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping + public ResponseEntity deleteAllWords() { + wordService.deleteAllWords(); + HttpHeaders headers = getCommonHeaders("Delete all words"); + return new ResponseEntity<>("All words deleted", headers, HttpStatus.OK); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + headers.add("word-count", String.valueOf(wordService.getWordCount())); + headers.add("object", "words"); + return headers; + } +} + diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/AppUser.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/AppUser.java new file mode 100644 index 000000000..fd6615611 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/AppUser.java @@ -0,0 +1,30 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.GenericGenerator; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppUser { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + private String id; + + private String userName; + private int age; + private String email; + private String password; + private int totalScore; + private boolean isActive; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + private GameProgress gameProgress; +} + + diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java new file mode 100644 index 000000000..778c9b3bd --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java @@ -0,0 +1,49 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.GenericGenerator; +import jakarta.persistence.OneToOne; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Enumerated; +import jakarta.persistence.EnumType; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; + +import java.util.Date; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GameProgress { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + private String id; + + private int currentScore; + + public enum Stage { + STAGE_01, STAGE_02, STAGE_03, STAGE_04, STAGE_05 + } + + @Enumerated(EnumType.STRING) + private Stage currentStage; + + @Temporal(TemporalType.TIMESTAMP) + private Date lastPlayedDate; + + private int wordsLearned; + + @OneToOne + @JoinColumn(name = "user_id") + private AppUser appUser; + + +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java new file mode 100644 index 000000000..a99da8c0e --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java @@ -0,0 +1,38 @@ +package dev.pronunciationAppBack.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class Pronunciation { + @Id + private String id; + private String audioDescription; + private long audioDuration; + private long audioSize; + private String audioUrl; + private String definition; + private String phoneticSpelling; + private String speakerGender; + public enum type { + RECORDED, SAMPLE + } + private type type; + + @JsonIgnore + @ManyToOne + @JoinColumn(name = "WORD_ID_FK") + private Word word; + + + +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWord.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWord.java new file mode 100644 index 000000000..3f5f86e66 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWord.java @@ -0,0 +1,33 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.Getter; +import lombok.Setter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.ToString; + +import jakarta.persistence.Id; +import java.util.Date; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class StageWord { + + @Id + private String id; + @Enumerated(EnumType.STRING) + private Status status; + private int listenedQty; + private Date lastUpdatedDateTime; + + public enum Status { + DONE, PENDING, FAIL + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java new file mode 100644 index 000000000..e6d2ae6dc --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java @@ -0,0 +1,113 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + + +import java.util.List; + +@Entity +public class Word { + + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + @OneToMany(mappedBy = "word") + private List pronunciations; + + public Word() {} + + public Word(String id, String wordName, String definition, String phoneticSpelling, String sentence, boolean isActive, int level) { + this.id = id; + this.wordName = wordName; + this.definition = definition; + this.phoneticSpelling = phoneticSpelling; + this.sentence = sentence; + this.isActive = isActive; + this.level = level; + } + + public List getPronunciations() { + return pronunciations; + } + + public void setPronunciations(List pronunciations) { + this.pronunciations = pronunciations; + } + + public String getId() { + return id; + } + + public String getWordName() { + return wordName; + } + + public String getDefinition() { + return definition; + } + + public String getPhoneticSpelling() { + return phoneticSpelling; + } + + public String getSentence() { + return sentence; + } + + public boolean isActive() { + return isActive; + } + + public int getLevel() { + return level; + } + + public void setId(String id) { + this.id = id; + } + + public void setWordName(String wordName) { + this.wordName = wordName; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public void setPhoneticSpelling(String phoneticSpelling) { + this.phoneticSpelling = phoneticSpelling; + } + + public void setSentence(String sentence) { + this.sentence = sentence; + } + + public void setActive(boolean active) { + isActive = active; + } + + public void setLevel(int level) { + this.level = level; + } + + @Override + public String toString() { + return "Word{" + + "id='" + id + '\'' + + ", wordName='" + wordName + '\'' + + ", definition='" + definition + '\'' + + ", phoneticSpelling='" + phoneticSpelling + '\'' + + ", sentence='" + sentence + '\'' + + ", isActive=" + isActive + + ", level=" + level + + '}'; + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AppUserRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AppUserRepository.java new file mode 100644 index 000000000..5b0235140 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AppUserRepository.java @@ -0,0 +1,15 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.AppUser; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + + +public interface AppUserRepository extends JpaRepository { + List findByIsActiveTrue(); + + AppUser findByEmail(String email); + + List findByAgeGreaterThan(int age); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java new file mode 100644 index 000000000..e9523bfb7 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java @@ -0,0 +1,19 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.GameProgress; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Date; +import java.util.List; + +public interface GameProgressRepository extends JpaRepository { + List findByCurrentStage(GameProgress.Stage stage); + + List findByLastPlayedDateAfter(Date date); + + List findByWordsLearnedGreaterThan(int count); + + GameProgress findByAppUserId(String userId); + + List findByCurrentScoreGreaterThan(int score); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java new file mode 100644 index 000000000..8fd7703f6 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java @@ -0,0 +1,16 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Pronunciation; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PronunciationRepository extends JpaRepository { + List findByType(Pronunciation.type type); + + List findBySpeakerGender(String gender); + + List findByWordId(String wordId); + + List findByPhoneticSpellingContaining(String phoneticSpelling); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordRepository.java new file mode 100644 index 000000000..24af36ca8 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordRepository.java @@ -0,0 +1,10 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.StageWord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface StageWordRepository extends JpaRepository { + List findByStatus(StageWord.Status status); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java new file mode 100644 index 000000000..e0d4c25cb --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java @@ -0,0 +1,10 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface WordRepository extends JpaRepository { + Word getWordById(String id); + Word getWordByPhoneticSpelling(String pronunciation); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AppUserService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AppUserService.java new file mode 100644 index 000000000..d9da88d81 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AppUserService.java @@ -0,0 +1,61 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.AppUser; +import dev.pronunciationAppBack.repository.AppUserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class AppUserService { + + @Autowired + private AppUserRepository appUserRepository; + + public List getAllUsers() { + return appUserRepository.findAll(); + } + + public Optional getUserById(String id) { + return appUserRepository.findById(id); + } + + public AppUser createUser(AppUser user) { + return appUserRepository.save(user); + } + + public AppUser updateUser(AppUser user) { + return appUserRepository.save(user); + } + + public void deleteUser(String id) { + appUserRepository.deleteById(id); + } + + public void deleteAllUsers() { + appUserRepository.deleteAll(); + } + + public boolean existsById(String id) { + return appUserRepository.existsById(id); + } + + public long getUserCount() { + return appUserRepository.count(); + } + + // Additional business logic can be added here + public List getActiveUsers() { + return appUserRepository.findByIsActiveTrue(); + } + + public AppUser getUserByEmail(String email) { + return appUserRepository.findByEmail(email); + } + + public List getUsersByAgeGreaterThan(int age) { + return appUserRepository.findByAgeGreaterThan(age); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java new file mode 100644 index 000000000..189311cc1 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java @@ -0,0 +1,70 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.GameProgress; +import dev.pronunciationAppBack.repository.GameProgressRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.Date; + +@Service +public class GameProgressService { + + @Autowired + private GameProgressRepository gameProgressRepository; + + public List getAllGameProgresses() { + return gameProgressRepository.findAll(); + } + + public Optional getGameProgressById(String id) { + return gameProgressRepository.findById(id); + } + + public GameProgress createGameProgress(GameProgress gameProgress) { + return gameProgressRepository.save(gameProgress); + } + + public GameProgress updateGameProgress(GameProgress gameProgress) { + return gameProgressRepository.save(gameProgress); + } + + public void deleteGameProgress(String id) { + gameProgressRepository.deleteById(id); + } + + public void deleteAllGameProgresses() { + gameProgressRepository.deleteAll(); + } + + public boolean existsById(String id) { + return gameProgressRepository.existsById(id); + } + + public long getGameProgressCount() { + return gameProgressRepository.count(); + } + + // Additional business logic methods + public List getGameProgressesByCurrentStage(GameProgress.Stage stage) { + return gameProgressRepository.findByCurrentStage(stage); + } + + public List getGameProgressesByLastPlayedDateAfter(Date date) { + return gameProgressRepository.findByLastPlayedDateAfter(date); + } + + public List getGameProgressesByWordsLearnedGreaterThan(int count) { + return gameProgressRepository.findByWordsLearnedGreaterThan(count); + } + + public GameProgress getGameProgressByAppUserId(String userId) { + return gameProgressRepository.findByAppUserId(userId); + } + + public List getGameProgressesByCurrentScoreGreaterThan(int score) { + return gameProgressRepository.findByCurrentScoreGreaterThan(score); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java new file mode 100644 index 000000000..be5020ce9 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java @@ -0,0 +1,66 @@ +package dev.pronunciationAppBack.service; + + +import dev.pronunciationAppBack.model.Pronunciation; +import dev.pronunciationAppBack.repository.PronunciationRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class PronunciationService { + + @Autowired + private PronunciationRepository pronunciationRepository; + + public List getAllPronunciations() { + return pronunciationRepository.findAll(); + } + + public Optional getPronunciationById(String id) { + return pronunciationRepository.findById(id); + } + + public Pronunciation createPronunciation(Pronunciation pronunciation) { + return pronunciationRepository.save(pronunciation); + } + + public Pronunciation updatePronunciation(Pronunciation pronunciation) { + return pronunciationRepository.save(pronunciation); + } + + public void deletePronunciation(String id) { + pronunciationRepository.deleteById(id); + } + + public void deleteAllPronunciations() { + pronunciationRepository.deleteAll(); + } + + public boolean existsById(String id) { + return pronunciationRepository.existsById(id); + } + + public long getPronunciationCount() { + return pronunciationRepository.count(); + } + + // Additional business logic methods + public List getPronunciationsByType(Pronunciation.type type) { + return pronunciationRepository.findByType(type); + } + + public List getPronunciationsBySpeakerGender(String gender) { + return pronunciationRepository.findBySpeakerGender(gender); + } + + public List getPronunciationsByWordId(String wordId) { + return pronunciationRepository.findByWordId(wordId); + } + + public List getPronunciationsByPhoneticSpelling(String phoneticSpelling) { + return pronunciationRepository.findByPhoneticSpellingContaining(phoneticSpelling); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordService.java new file mode 100644 index 000000000..37f222ad2 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordService.java @@ -0,0 +1,53 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.StageWord; +import dev.pronunciationAppBack.repository.StageWordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class StageWordService { + + @Autowired + private StageWordRepository stageWordRepository; + + public List getAllStageWords() { + return stageWordRepository.findAll(); + } + + public Optional getStageWordById(String id) { + return stageWordRepository.findById(id); + } + + public StageWord createStageWord(StageWord stageWord) { + return stageWordRepository.save(stageWord); + } + + public StageWord updateStageWord(StageWord stageWord) { + return stageWordRepository.save(stageWord); + } + + public void deleteStageWord(String id) { + stageWordRepository.deleteById(id); + } + + public void deleteAllStageWords() { + stageWordRepository.deleteAll(); + } + + public boolean existsById(String id) { + return stageWordRepository.existsById(id); + } + + public long getStageWordCount() { + return stageWordRepository.count(); + } + + // Additional business logic can be added here + public List getStageWordsByStatus(StageWord.Status status) { + return stageWordRepository.findByStatus(status); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java new file mode 100644 index 000000000..fba5d3e1b --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java @@ -0,0 +1,53 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Optional getWordById(String id) { + return Optional.ofNullable(wordRepository.getWordById(id)); + } + + public Word createWord(Word word) { + return wordRepository.save(word); + } + + public Word updateWord(Word word) { + return wordRepository.save(word); + } + + public void deleteWord(String id) { + wordRepository.deleteById(id); + } + + public void deleteAllWords() { + wordRepository.deleteAll(); + } + + public boolean existsById(String id) { + return wordRepository.existsById(id); + } + + public long getWordCount() { + return wordRepository.count(); + } + + // Additional business logic can be added here + public Word getWordByPhoneticSpelling(String pronunciation) { + return wordRepository.getWordByPhoneticSpelling(pronunciation); + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/resources/application.properties b/backend/pronunciationAppBack/src/main/resources/application.properties index edb9ea0d2..83cb16f3e 100644 --- a/backend/pronunciationAppBack/src/main/resources/application.properties +++ b/backend/pronunciationAppBack/src/main/resources/application.properties @@ -1 +1,20 @@ spring.application.name=pronunciationAppBack + +# H2 DATABASE SERVER +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true + +# H2 IN MEMORY +#spring.datasource.url=jdbc:h2:mem:testdb +#spring.datasource.username=sa +#spring.datasource.password= + + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/pronunciationDB/pronunciationDB.db +spring.datasource.username=albert +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=none \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java index ea361613f..63c919e16 100644 --- a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java +++ b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java @@ -1,13 +1,67 @@ +/* package dev.pronunciationAppBack; +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; -@SpringBootTest -class PronunciationAppBackApplicationTests { +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class PronunciationAppBackApplicationTests { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private WordRepository wordRepository; + + @Test + public void testCreateWord() { + Word word = new Word("1", "Example", "A thing characteristic of its kind", "ɪɡˈzæmpəl", "This is an example sentence.", true, 1); + // assign the word object to the repository and save to H2 + Word savedWord = wordRepository.save(word); + assertThat(savedWord).isNotNull(); + assertThat(savedWord.getId()).isEqualTo("1"); + } + + @Test + public void testReadWord() { + Word word = new Word("2", "Test", "A procedure to evaluate", "test", "This is a test sentence.", true, 2); + entityManager.persist(word); + + Word foundWord = wordRepository.findById("2").orElse(null); + assertThat(foundWord).isNotNull(); + assertThat(foundWord.getWordName()).isEqualTo("Test"); + } @Test - void contextLoads() { + public void testUpdateWord() { + Word word = new Word("3", "Update", "To bring up to date", "ˈʌpdeɪt", "This word will be updated.", true, 3); + entityManager.persist(word); + + Word wordToUpdate = wordRepository.findById("3").orElse(null); + assertThat(wordToUpdate).isNotNull(); + wordToUpdate.setDefinition("To make something more modern or up to date"); + wordRepository.save(wordToUpdate); + + Word updatedWord = wordRepository.findById("3").orElse(null); + assertThat(updatedWord).isNotNull(); + assertThat(updatedWord.getDefinition()).isEqualTo("To make something more modern or up to date"); } + @Test + public void testDeleteWord() { + Word word = new Word("4", "Delete", "To remove or erase", "dɪˈliːt", "This word will be deleted.", true, 4); + entityManager.persist(word); + + wordRepository.deleteById("4"); + + Word deletedWord = wordRepository.findById("4").orElse(null); + assertThat(deletedWord).isNull(); + } } +*/ diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationTest.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationTest.java new file mode 100644 index 000000000..49d6a8707 --- /dev/null +++ b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationTest.java @@ -0,0 +1,59 @@ +package dev.pronunciationAppBack; + +import dev.pronunciationAppBack.repository.PronunciationRepository; +import dev.pronunciationAppBack.repository.WordRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.model.Pronunciation; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Optional; + +@SpringBootTest +public class PronunciationTest { + + @Autowired + WordRepository wordRepository; + @Autowired + PronunciationRepository pronunciationRepository; + + @Test + public void AssignTestWord(){ + + //Word w1 = new Word(); + + Pronunciation p1 = new Pronunciation(); + p1.setId("1"); + p1.setAudioDescription("test"); + p1.setAudioDuration(1); + p1.setAudioSize(1); + p1.setAudioUrl("test"); + p1.setDefinition("test"); + p1.setPhoneticSpelling("test"); + p1.setSpeakerGender("test"); + p1.setType(Pronunciation.type.SAMPLE); + + pronunciationRepository.save(p1); + + // get word by id from repository + // by JPA repository by id returning optional container Word/Null + Optional optionalWord = wordRepository.findById("8f7d1b9e3a2c5f6e"); + System.out.println(optionalWord); + + if (optionalWord.isPresent()) { + // get word from optional container + p1.setWord(optionalWord.get()); + pronunciationRepository.save(p1); + } + + + + // pronunciationRepository.save(p1); + // w1.getPronunciations().add(p1); + // wordRepository.save(w1); + + } +} diff --git a/backend/resources/Test-JUnit DB/test-JUnit-Word.md b/backend/resources/Test-JUnit DB/test-JUnit-Word.md new file mode 100644 index 000000000..e9312dc38 --- /dev/null +++ b/backend/resources/Test-JUnit DB/test-JUnit-Word.md @@ -0,0 +1,171 @@ +# Test JUnit Word H2 DB + +## Test + +Here are 4 simple test cases for CRUD operations using `JUnit` and `Spring Boot` for the Word `entity`: + +```java +package dev.pronunciationAppBack; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class WordRepositoryTest { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private WordRepository wordRepository; + + @Test + public void testCreateWord() { + Word word = new Word("1", "Example", "A thing characteristic of its kind", "ɪɡˈzæmpəl", "This is an example sentence.", true, 1); + Word savedWord = wordRepository.save(word); + assertThat(savedWord).isNotNull(); + assertThat(savedWord.getId()).isEqualTo("1"); + } + + @Test + public void testReadWord() { + Word word = new Word("2", "Test", "A procedure to evaluate", "test", "This is a test sentence.", true, 2); + entityManager.persist(word); + + Word foundWord = wordRepository.findById("2").orElse(null); + assertThat(foundWord).isNotNull(); + assertThat(foundWord.getWordName()).isEqualTo("Test"); + } + + @Test + public void testUpdateWord() { + Word word = new Word("3", "Update", "To bring up to date", "ˈʌpdeɪt", "This word will be updated.", true, 3); + entityManager.persist(word); + + Word wordToUpdate = wordRepository.findById("3").orElse(null); + assertThat(wordToUpdate).isNotNull(); + wordToUpdate.setDefinition("To make something more modern or up to date"); + wordRepository.save(wordToUpdate); + + Word updatedWord = wordRepository.findById("3").orElse(null); + assertThat(updatedWord).isNotNull(); + assertThat(updatedWord.getDefinition()).isEqualTo("To make something more modern or up to date"); + } + + @Test + public void testDeleteWord() { + Word word = new Word("4", "Delete", "To remove or erase", "dɪˈliːt", "This word will be deleted.", true, 4); + entityManager.persist(word); + + wordRepository.deleteById("4"); + + Word deletedWord = wordRepository.findById("4").orElse(null); + assertThat(deletedWord).isNull(); + } +} +``` + +These test cases cover the basic CRUD (Create, Read, Update, Delete) operations for the Word entity. Here's a brief explanation of each test: + +1. `testCreateWord`: Tests the creation of a new Word entity. +2. `testReadWord`: Tests reading a Word entity from the database. +3. `testUpdateWord`: Tests updating an existing Word entity. +4. `testDeleteWord`: Tests deleting a Word entity from the database. + +## Repository + +Note that these tests assume you have a `WordRepository` interface that extends `JpaRepository`. You'll need to create this interface in your project: + +```java +package dev.pronunciationAppBack; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WordRepository extends JpaRepository { +} +``` + +Also, make sure you have the necessary dependencies in your `pom.xml` or `build.gradle` file for Spring Boot Test and JPA Test. + +## H2 and application.properties + +> For these JUnit tests to run properly with an in-memory database, we need to add the `H2 database` dependency and configure the `application.properties` file. + +Here's what you need to add: + +1. H2 Database Dependency: + Add this to your `pom.xml` if you're using Maven: + +```xml + + com.h2database + h2 + test + +``` + +Or if you're using Gradle, add this to your `build.gradle`: + +```gradle +dependencies { + testImplementation 'com.h2database:h2' +} +``` + +2. Application Properties: + Create a file named `application.properties` in your `src/test/resources` directory with the following content: + +```properties +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 + +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +``` + +These settings will: + +- Configure an **in-memory H2 database for testing** +- Set up the database to create tables based on your entities and drop them after the tests +- Show SQL statements in the console, which can be helpful for debugging + +## Local DB + +First you must create the DB: + +```properties +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=create +``` + +Once created, change DDL to none + +```properties +spring.application.name=pronunciationAppBack + +# H2 DATABASE SERVER +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true + +# H2 IN MEMORY +#spring.datasource.url=jdbc:h2:mem:testdb +#spring.datasource.username=sa +#spring.datasource.password= + + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/pronunciationDB/pronunciationDB.db +spring.datasource.username=albert +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=none +``` diff --git a/backend/resources/annotations/Param-PathVariable.md b/backend/resources/annotations/Param-PathVariable.md new file mode 100644 index 000000000..386848a2f --- /dev/null +++ b/backend/resources/annotations/Param-PathVariable.md @@ -0,0 +1,66 @@ +# Param vs Path Variable + +## Using @DeleteMapping in Spring Boot with Postman + +Spring Boot's `@DeleteMapping` annotation simplifies the process of handling HTTP DELETE requests. + +> The `@DeleteMapping` annotation in Spring Boot provides a clean and efficient way to handle DELETE requests. Whether you choose to use path variables or query parameters depends on your API design preferences and requirements. Postman is an excellent tool for testing these endpoints, allowing you to easily send DELETE requests and verify the results. + +## DeleteWord Example + +Here's a simple example of a delete operation in a Spring Boot controller: + +```java +@RestController +@RequestMapping("/words") +public class WordController { + + @Autowired + private WordRepository wordRepository; + + @DeleteMapping("/{id}") + public String deleteWord(@PathVariable("id") String idToDelete) { + wordRepository.deleteById(idToDelete); + return "Word deleted"; + } +} +``` + +In this example, the `deleteWord` method is mapped to handle DELETE requests to the `/words/{id}` endpoint[1][4]. The `@PathVariable` annotation binds the `id` from the URL to the `idToDelete` parameter[3]. + +## Testing with Postman + +To test this endpoint using Postman, follow these steps: + +1. Open Postman and create a new request. +2. Set the HTTP method to DELETE. +3. Enter the URL: `http://localhost:8080/words/{id}` (replace `{id}` with the actual ID you want to delete). +4. Click the "Send" button to execute the request. + +### Using Path Variable + +For our `deleteWord` example, we're using a path variable. The ID is part of the URL path: + +``` +DELETE http://localhost:8080/words/123 +``` + +Here, `123` is the ID of the word to be deleted[5]. + +### Using Query Parameter (Alternative Approach) + +While our example uses a path variable, you could also design your endpoint to use a query parameter: + +```java +@DeleteMapping +public String deleteWord(@RequestParam("id") String idToDelete) { + wordRepository.deleteById(idToDelete); + return "Word deleted"; +} +``` + +To test this with Postman: + +1. Set the URL to `http://localhost:8080/words` +2. Add a query parameter: Key: `id`, Value: `123` +3. The full URL will look like: `http://localhost:8080/words?id=123` diff --git a/backend/resources/annotations/annotatin-xml.md b/backend/resources/annotations/annotatin-xml.md new file mode 100644 index 000000000..cb7005f65 --- /dev/null +++ b/backend/resources/annotations/annotatin-xml.md @@ -0,0 +1,120 @@ +# Annotations and xml + +> Spring Boot has significantly simplified configuration through extensive use of annotations, reducing the need for XML-based configuration. +> +> This shift towards annotation-based configuration became more prominent with Spring 2.1 and has continued to evolve in subsequent versions. + +Here's a simple example comparing XML-based configuration with annotation-based configuration in Spring Boot: + +XML Configuration: + +```xml + + + + + +``` + +Equivalent Annotation-based Configuration: + +```java +@Service +public class MyService { + @Autowired + private MyRepository repository; +} + +@Repository +public class MyRepository { + // Implementation +} +``` + +In this example, the `@Service` and `@Repository` annotations replace the XML bean definitions, while `@Autowired` handles dependency injection[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +#### Key Annotations in Spring Boot + +1. **@SpringBootApplication**: This annotation combines `@Configuration`, `@EnableAutoConfiguration`, and `@ComponentScan`. It's typically used on the main class of a Spring Boot application1. + +2. **@Component**: A generic stereotype for any Spring-managed component[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +3. **@Controller**, **@Service**, **@Repository**: Specializations of `@Component` for more specific use cases[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +4. **@Autowired**: Used for automatic dependency injection[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +5. **@Configuration**: Indicates that a class declares one or more `@Bean` methods and may be processed by the Spring container to generate bean definitions1. + +6. **@Bean**: Indicates that a method produces a bean to be managed by the Spring container1. + +7. **@Value**: Used to inject values from properties files into components[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +#### Advantages of Annotation-based Configuration + +1. **Reduced boilerplate**: Annotations significantly reduce the amount of configuration code needed1. + +2. **Type safety**: Annotations are checked at compile-time, reducing runtime errors[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +3. **Improved readability**: Annotations make the code more self-documenting1. + +4. **Easier refactoring**: Changes to class names or package structures are automatically reflected in the configuration[3](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html). + +While annotations have become the preferred method of configuration in Spring Boot, it's still possible to use XML configuration when needed, especially for legacy systems or specific use cases. Spring Boot allows for mixing both approaches, providing flexibility in configuration strategies[2](https://www.springboottutorial.com/spring-boot-java-xml-context-configuration)[4](https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html). + +### Citations: + +1. [Spring Boot Tutorial: Mixing XML and Annotation Configuration - YouTube](https://www.youtube.com/watch?v=6arSdLciC_k) +2. [Java Annotation and XML Bean Configurations with Spring Boot | Spring Boot Tutorial](https://www.springboottutorial.com/spring-boot-java-xml-context-configuration) +3. [Annotation-based Container Configuration :: Spring Framework](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html) +4. [XML Schema-based configuration](https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html) +5. [Do we need xml file for doing Spring configuration using Annotation? - Stack Overflow](https://stackoverflow.com/questions/38322967/do-we-need-xml-file-for-doing-spring-configuration-using-annotation) +6. [Spring Boot XML Configuration Example](https://www.javaguides.net/2018/09/spring-boot-xml-configuration-example.html) +7. [Spring Annotation and XML Based Configuration - Studytonight](https://www.studytonight.com/spring-framework/spring-annotation-and-xml-based-configuration) +8. [How can I use Spring Boot auto-configured beans in XML configuration files? - Stack Overflow](https://stackoverflow.com/questions/25495629/how-can-i-use-spring-boot-auto-configured-beans-in-xml-configuration-files) + +## Example + +```java +@JoinTable(name = "USER_PRODUCT" +joinColumns = @JoinColumn(name = "USER_FK"), +inverseJoinColumns = @JoinColumn(name = "PRODUCT_FK")) +public Set products +``` + +To configure the equivalent of the given `@JoinTable` annotation in `XML` for Spring MVC, you would use the following XML configuration: + +```xml + + + + + + + + + + + + + + + + + + + + +``` + +This `XML` configuration creates a many-to-many relationship between the `User` and `Product` entities using a join table named "`USER_PRODUCT`". The join column for the User entity is "`USER_FK`", and the inverse join column for the Product entity is "`PRODUCT_FK`"[1](https://stackoverflow.com/questions/30454300/spring-mvc-xml-configuration)[3](https://dzone.com/articles/spring-mvc-and-java-based-configuration-1). + +### Citations: + +1. [java - Spring MVC xml configuration - Stack Overflow](https://stackoverflow.com/questions/30454300/spring-mvc-xml-configuration) +2. [SQL INNER JOIN](https://www.w3schools.com/sql/sql_join_inner.asp) +3. https://dzone.com/articles/spring-mvc-and-java-based-configuration-1 +4. [Introduction to Spring Data JPA Part 8: Many-to-Many Bidirectional](https://dzone.com/articles/introduction-to-spring-data-jpa-part-8-many-to-man) +5. https://proliferay.com/xml-configuration-files-for-spring-mvc/ +6. [EclipseLink/Examples/JPA/2.0/ElementCollections - Eclipsepedia](https://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/ElementCollections) +7. [Spring MVC Basic Example Without Any XML Configuration.](https://codedeal.wixsite.com/codedeal/single-post/spring-mvc-basic-example-without-any-xml-configuration) +8. [Spring MVC :: Spring Boot](https://docs.spring.io/spring-boot/how-to/spring-mvc.html) diff --git a/backend/resources/cli-spring/create-spring-repo.sh b/backend/resources/cli-spring/create-spring-repo.sh new file mode 100755 index 000000000..cae608119 --- /dev/null +++ b/backend/resources/cli-spring/create-spring-repo.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Set default values +JAVA_VERSION=21 +SPRING_BOOT_VERSION=3.4.2 +GROUP_ID="com.example" # You can change this or prompt the user for it +DEPENDENCIES="web,data-jpa,h2,devtools,lombok" #define dependencies + +# Prompt the user for the project name +read -p "Enter the Spring Boot project name: " PROJECT_NAME + +# Validate project name (optional, but recommended) +if [[ -z "$PROJECT_NAME" ]]; then + echo "Error: Project name cannot be empty." + exit 1 +fi + +# Prompt user for GitHub username +read -p "Enter your Github username: " GITHUB_USERNAME + +# Initialize the Spring Boot project +echo "Initializing Spring Boot project '$PROJECT_NAME'..." +spring init \ + --dependencies="$DEPENDENCIES" \ + --build=maven \ + --java-version="$JAVA_VERSION" \ + --packaging=jar \ + --version="$SPRING_BOOT_VERSION" \ + --name="$PROJECT_NAME" \ + --groupId="$GROUP_ID" \ + "$PROJECT_NAME" + +# Change directory to the project +cd "$PROJECT_NAME" || exit + +# Initialize Git repository +echo "Initializing Git repository..." +git init + +# Configure Git user (you might want to prompt the user for these, or set them globally) +git config user.name "$GITHUB_USERNAME" +git config user.email "$GITHUB_USERNAME@gmail.com" #modify + +# Create GitHub repository +echo "Creating GitHub repository '$PROJECT_NAME'..." +gh repo create "$PROJECT_NAME" --public + +# Add remote origin +echo "Adding remote origin..." +git remote add origin "https://github.com/$GITHUB_USERNAME/$PROJECT_NAME.git" + +# Add all files, commit, and push +echo "Adding files, committing, and pushing to GitHub..." +git add . +git commit -m "Initial commit" +git push -u origin master + +echo "Project '$PROJECT_NAME' created and pushed to GitHub successfully!" +exit 0 + diff --git a/backend/resources/cli-spring/spring-boot-cli.md b/backend/resources/cli-spring/spring-boot-cli.md new file mode 100644 index 000000000..6a3d0aece --- /dev/null +++ b/backend/resources/cli-spring/spring-boot-cli.md @@ -0,0 +1,77 @@ +# Build Spring Boot project by CLI + +- [Installing the CLI :: Spring Boot](https://docs.spring.io/spring-boot/cli/installation.html) +- [Using the CLI :: Spring Boot](https://docs.spring.io/spring-boot/cli/using-the-cli.html) +- https://start.spring.io/ + +Building by CLI Spring Boot projects is easy but your must be careful with no using a outdated versoin of Spring Boot CLI. + +To resolve this, you need to properly install the Spring Boot CLI, uninstalling the current/previous Spring CLI installation. + +If you do not have any previous or current installation go to step 3. + +Here's how to do it: + +1. First, uninstall the current Spring CLI installation: + +```bash +sudo apt remove spring +``` + +2. Install SDKMAN, which is a better tool for managing Spring Boot CLI: + +```bash +curl -s "https://get.sdkman.io" | bash source "$HOME/.sdkman/bin/sdkman-init.sh" +``` + +3. Use SDKMAN to install the latest Spring Boot CLI: + +```bash +sdk install springboot` +``` + +4. Verify the installation: + +```bash +spring --version +``` + +This should now show the latest version of Spring Boot CLI. + +5. Now, run `spring init`command: + +```bash +spring init \ + --dependencies=web,data-jpa,h2,devtools,lombok \ + --build=maven \ + --java-version=21 \ + --packaging=jar \ + --version=3.4.2 \ + --name=borrowabook \ + borrowabook +``` + +> If you still encounter issues, ensure that Java 21 is installed on your system, as Spring Boot 3.4.2 requires Java 17 or later. You can install Java 21 using: + +```bash +sudo apt install openjdk-21-jdk +``` + +After installing Java 21, set it as the default Java version: + +```bash +sudo update-alternatives --config java +``` + +Choose the option corresponding to Java 21. + +### Citations: + +1. [Spring Boot CLI Setup and HelloWorld Example | DigitalOcean](https://www.digitalocean.com/community/tutorials/spring-boot-cli-setup-and-helloworld-example) +2. [How to install spring boot CLI on Mac? - Stack Overflow](https://stackoverflow.com/questions/47704002/how-to-install-spring-boot-cli-on-mac) +3. [Spring Cloud CLI installation with Spring Boot 1.4 CLI - ClassNotFoundException - org.springframework.boot.cli.command.CommandFactory - Stack Overflow](https://stackoverflow.com/questions/38862408/spring-cloud-cli-installation-with-spring-boot-1-4-cli-classnotfoundexception) +4. [Installing the CLI :: Spring Boot](https://docs.spring.io/spring-boot/cli/installation.html) +5. [Install instructions do not work with spring-boot-cli 3.0.0 · Issue #180 · spring-cloud/spring-cloud-cli · GitHub](https://github.com/spring-cloud/spring-cloud-cli/issues/180) +6. [Spring Boot CLI :: Spring Boot](https://docs.spring.io/spring-boot/cli/index.html) +7. [Failed to run spring cloud cli · Issue #80 · spring-cloud/spring-cloud-cli · GitHub](https://github.com/spring-cloud/spring-cloud-cli/issues/80) +8. [Reddit - Dive into anything](https://www.reddit.com/r/SpringBoot/comments/1fm7ik0/install_springboot_cli_with_homebrew_doesnt/) diff --git a/backend/resources/containers/Containers-Spring.md b/backend/resources/containers/Containers-Spring.md new file mode 100644 index 000000000..4da920796 --- /dev/null +++ b/backend/resources/containers/Containers-Spring.md @@ -0,0 +1,150 @@ +# Popular Containers in Spring Boot + +> Containers in Java are typically defined by their ability to hold and manage collections of objects or references. + +However, for example, List`and`Optional` serve fundamentally different purposes: + +- `List` is a collection container designed to store multiple elements with order and allow dynamic manipulation. +- `Optional` is a wrapper to explicitly handle the presence or absence of a single value, preventing null reference issues. + +While both can "contain" elements, they solve distinct programming challenges: data storage versus null-safety management. + +#### Wrapper and Collection + +> A **wrapper** is a layer of code that "wraps around" something simpler, adding extra functionality or protection. Like a protective cover that makes something easier to use or more powerful. +> +> In Java, a wrapper takes a basic object or value and provides additional methods or behaviors to interact with it more conveniently. + + + +> A **Collection** is a more structured way of storing and managing those items, with methods to add, remove, and manipulate the group of elements. +> +> An **Iterable** is something you can loop through, like a collection of items. +> +> + +**Container Comparison: List vs Optional** + +| Characteristic | List | Optional | +| -------------------- | ------------------------------- | ------------------------------------------- | +| **Purpose** | Store multiple elements | Represent optional value | +| **Nullability** | Can contain null elements | Explicitly prevents null | +| **Size** | Dynamic, variable length | Always contains 0 or 1 element | +| **Mutability** | Mutable (add/remove elements) | Immutable | +| **Creation** | `new ArrayList<>()` | `Optional.of()`, `Optional.empty()` | +| **Common Methods** | `.add()`, `.remove()`, `.get()` | `.isPresent()`, `.orElse()`, `.ifPresent()` | +| **Java 8+ Feature** | Pre-Java 8 | Introduced in Java 8 | +| **Typical Use Case** | Collection storage | Avoiding null checks | +| **Stream Support** | `.stream()` directly | Treated as stream with `.stream()` | +| **Performance** | Higher memory overhead | Lightweight wrapper | + +### Response Containers + +1. **ResponseEntity** + + - Full control over HTTP response + - Set status codes, headers, body + - Example: + + ```java + return ResponseEntity.ok(word); + return ResponseEntity.notFound().build(); + return ResponseEntity.status(HttpStatus.CREATED).body(word); + ``` + +2. **Optional** + + - Prevent null pointer exceptions + - Avoid explicit null checks + - Example: + + ```java + Optional word = repository.findById(id); + return word.orElseThrow(() -> new ResourceNotFoundException()); + ``` + +3. **Page** + + - Pagination support + - Metadata about result set + - Example: + + ```java + Page words = repository.findAll(PageRequest.of(0, 10)); + ``` + +4. **Mono** and **Flux** (Reactive Programming) + + - Asynchronous data streams + - Non-blocking operations + - Example: + + ```java + Mono wordMono = wordRepository.findById(id); + Flux wordFlux = wordRepository.findAll(); + ``` + +5. **Resource** (HATEOAS) + + - Include hyperlinks in responses + - Support for hypermedia-driven APIs + - Example: + + ```java + Resource resource = new Resource<>(word); + resource.add(linkTo(methodOn(WordController.class).getWord(id)).withSelfRel()); + ``` + +### Key Benefits + +- Type safety +- Explicit error handling +- Flexible response management +- Support for modern architectural patterns + +## Non-Response + +### Data Containers + +1. **List** + + - Basic collection of elements + - Dynamic sizing + - Example: `List words = new ArrayList<>();` + +2. **Set** + + - Unique elements + - No duplicates + - Example: `Set uniqueWordNames = new HashSet<>();` + +3. **Map** + + - Key-value pairs + - Fast lookups + - Example: `Map wordMap = new HashMap<>();` + +4. **Stream** + + - Functional-style operations + - Lazy evaluation + - Example: `words.stream().filter(w -> w.getLevel() > 2)` + +5. **CompletableFuture** + + - Asynchronous computation + - Chaining operations + - Example: `CompletableFuture wordFuture = CompletableFuture.supplyAsync(() -> createWord());` + +6. **Queue** + + - First-In-First-Out (FIFO) + - Task scheduling + - Example: `Queue wordQueue = new LinkedList<>();` + +### Key Characteristics + +- Thread-safety +- Performance optimization +- Flexible data manipulation +- Support for functional programming diff --git a/backend/resources/CreateSpringBootproject.md b/backend/resources/create project/CreateSpringBootproject.md similarity index 100% rename from backend/resources/CreateSpringBootproject.md rename to backend/resources/create project/CreateSpringBootproject.md diff --git a/backend/resources/create project/create-spring-boot-b.png b/backend/resources/create project/create-spring-boot-b.png new file mode 100644 index 000000000..7230e9fdd Binary files /dev/null and b/backend/resources/create project/create-spring-boot-b.png differ diff --git a/backend/resources/create project/create-spring-boot.png b/backend/resources/create project/create-spring-boot.png new file mode 100644 index 000000000..415b8d1dd Binary files /dev/null and b/backend/resources/create project/create-spring-boot.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-project-structure.png b/backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png similarity index 100% rename from backend/resources/pronunciationAppBack-v0.0-project-structure.png rename to backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png diff --git a/backend/resources/start-spring-io-create-project.png b/backend/resources/create project/pronunciationAppBack-v0.0-spring-io-create-project.png similarity index 100% rename from backend/resources/start-spring-io-create-project.png rename to backend/resources/create project/pronunciationAppBack-v0.0-spring-io-create-project.png diff --git a/backend/resources/images/pronunciationAppBack-v0.0-api-rest-words.png b/backend/resources/images/pronunciationAppBack-v0.0-api-rest-words.png new file mode 100644 index 000000000..43e002693 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-api-rest-words.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller-2.png b/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller-2.png new file mode 100644 index 000000000..e97453151 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller-2.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller.png b/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller.png new file mode 100644 index 000000000..2d4acde25 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-basic-CRUD-controller.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-db-2.png b/backend/resources/images/pronunciationAppBack-v0.0-db-2.png new file mode 100644 index 000000000..c6672f82a Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-db-2.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-db.png b/backend/resources/images/pronunciationAppBack-v0.0-db.png new file mode 100644 index 000000000..2a496ab0a Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-db.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-project-structure-2.png b/backend/resources/images/pronunciationAppBack-v0.0-project-structure-2.png new file mode 100644 index 000000000..22a050a1f Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-project-structure-2.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.0-project-structure.png b/backend/resources/images/pronunciationAppBack-v0.0-project-structure.png new file mode 100644 index 000000000..3dbc4ee10 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.0-project-structure.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.2-checkhealth.png b/backend/resources/images/pronunciationAppBack-v0.2-checkhealth.png new file mode 100644 index 000000000..c62a94f9e Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.2-checkhealth.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.2-project-structure.png b/backend/resources/images/pronunciationAppBack-v0.2-project-structure.png new file mode 100644 index 000000000..d376fc077 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.2-project-structure.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.3-db.png b/backend/resources/images/pronunciationAppBack-v0.3-db.png new file mode 100644 index 000000000..5d57a59ec Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.3-db.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.3-model.png b/backend/resources/images/pronunciationAppBack-v0.3-model.png new file mode 100644 index 000000000..cbe96193d Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.3-model.png differ diff --git a/backend/resources/images/pronunciationAppBack-v0.3-postman.png b/backend/resources/images/pronunciationAppBack-v0.3-postman.png new file mode 100644 index 000000000..2b4fcf2a3 Binary files /dev/null and b/backend/resources/images/pronunciationAppBack-v0.3-postman.png differ diff --git a/backend/resources/jpa/invalidate-cache/InvalidateCache.md b/backend/resources/jpa/invalidate-cache/InvalidateCache.md new file mode 100644 index 000000000..4d794da5d --- /dev/null +++ b/backend/resources/jpa/invalidate-cache/InvalidateCache.md @@ -0,0 +1,14 @@ +# Invalidate Cache + +> When switching branches in IntelliJ IDEA for Spring Boot projects a common issue appear: **no class is load into the IDE ** + +Here are some steps to resolve the problem: + +1. Invalidate caches and restart: Go to File > Invalidate Caches / Restart[5](https://stackoverflow.com/questions/12132003/getting-cannot-find-symbol-in-java-project-in-intellij). +2. Rebuild the project: Select Build > Rebuild Project[5](https://stackoverflow.com/questions/12132003/getting-cannot-find-symbol-in-java-project-in-intellij). +3. Reimport Maven dependencies: Right-click on the project, select Maven > Reimport[5](https://stackoverflow.com/questions/12132003/getting-cannot-find-symbol-in-java-project-in-intellij). +4. Enable annotation processing: Go to Preferences > Build, Execution, Deployment > Compiler > Annotation Processors and check "Enable annotation processing"[1](https://stackoverflow.com/questions/53031917/logback-references-are-red-in-intellij-using-spring-boot/53036773). +5. Ensure the Lombok plugin is installed: Go to Preferences > Plugins, search for Lombok, and install it if not already present[1](https://stackoverflow.com/questions/53031917/logback-references-are-red-in-intellij-using-spring-boot/53036773). +6. Check Java version: Make sure IntelliJ is set to use the correct Java version for your project[2](https://stackoverflow.com/questions/11632120/why-so-red-intellij-seems-to-think-every-declaration-method-cannot-be-found-res). +7. Verify source folders: Ensure that your source folders are correctly marked as "Sources" in the Project Structure (File > Project Structure > Modules > Sources)[4](https://www.jmri.org/help/en/html/doc/Technical/IntelliJ.shtml). +8. Delete the .idea folder: If the issue persists, try deleting the .idea folder and reimporting the project[2](https://stackoverflow.com/questions/11632120/why-so-red-intellij-seems-to-think-every-declaration-method-cannot-be-found-res). diff --git a/backend/resources/jpa/invalidate-cache/indexing-intelljidea.png b/backend/resources/jpa/invalidate-cache/indexing-intelljidea.png new file mode 100644 index 000000000..71ad9dd22 Binary files /dev/null and b/backend/resources/jpa/invalidate-cache/indexing-intelljidea.png differ diff --git a/backend/resources/jpa/invalidate-cache/invalidate-cache.png b/backend/resources/jpa/invalidate-cache/invalidate-cache.png new file mode 100644 index 000000000..e7d0d091a Binary files /dev/null and b/backend/resources/jpa/invalidate-cache/invalidate-cache.png differ diff --git a/backend/resources/jpa/jpa-hibernate-jdbc.png b/backend/resources/jpa/jpa-hibernate-jdbc.png new file mode 100644 index 000000000..cf6d0e302 Binary files /dev/null and b/backend/resources/jpa/jpa-hibernate-jdbc.png differ diff --git a/backend/resources/jpa/jpa.md b/backend/resources/jpa/jpa.md new file mode 100644 index 000000000..1b828ad61 --- /dev/null +++ b/backend/resources/jpa/jpa.md @@ -0,0 +1,119 @@ +# JPA + +- [Spring Boot: Data & DB – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-data.html) + +- [Spring Boot: JPA & DI – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa.html) + +- [Spring Boot: JPA Mappings – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-2.html) + +- [Spring Boot: JPA Relationships – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-3.html) + +- [Spring Boot: JPA Queries – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-4.html) + +- [Spring Boot: JPA Inherence – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-5.html) + +- [Spring Boot: Scaling – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-scaling.html) + +## Summary + +JPA stands for `Java Persistence API`. + +It is a Java specification for **managing, persisting, and accessing relational data** in Java applications. + +JPA is a **standard API for ORM (Object-Relational Mapping)** and provides a way to map Java objects to relational databases + +## Spring Boot DAL/DAO + +**ORM (Object-Relational Mapping)** + +> ORM is the concept of mapping object-oriented domain models to relational database tables. JPA and Hibernate are examples of ORM frameworks. + +```bash +App +└── Spring Data JPA + └── JPA (Java Persistence API) + └── Hibernate + └── JDBC (Java Database Connectivity) + └── Relational Database +``` + +**Application Layer** + +At the top, we have the application code that needs to interact with data. + +**Repository (Spring Data JPA)** + +Spring Data JPA provides a high-level abstraction for data access. It simplifies database operations by allowing developers to define interfaces that extend JpaRepository. For example: + +```java +public interface UserRepository extends JpaRepository { + List findByLastName(String lastName); +} +``` + +This interface automatically generates methods for common database operations. + +**JPA (Java Persistence API)** + +JPA is a specification that defines how to persist data in Java applications. + +**It's not an implementation**, but a set of interfaces and annotations that describe how to map Java objects to database tables. For example: + +```java +@Entity +public class User { + @Id + private Long id; + private String firstName; + private String lastName; +} +``` + +**Hibernate** + +Hibernate is a popular implementation of JPA. + + It provides the actual code that performs the object-relational mapping (ORM) based on JPA specifications. Hibernate translates JPA annotations and method calls into SQL queries. + +**JDBC (Java Database Connectivity)** + +JDBC is a low-level API for connecting Java applications to databases. + +It provides a set of Java classes and interfaces that send SQL statements to the database and process the results. Hibernate uses JDBC under the hood to communicate with the database. + +**Database** + +At the bottom is the actual database system, such as MySQL, PostgreSQL, or Oracle. + +## DAL/DAO for a 5yo + +Imagine you're building a house: + +- The **Application** is like the entire house. +- The **Repository** is like a smart assistant that helps you organize and find things in the house. +- **JPA** is like a blueprint that describes how rooms should be organized. +- **Hibernate** is the construction team that builds the rooms according to the blueprint. +- **JDBC** is like the basic tools (hammers, nails) the construction team uses. +- The **Database** is the foundation and structure of the house. +- **ORM** is the process of arranging the rooms to match how you want to use them. + +## Vendors and Context + +These technologies work together to simplify database operations in Java applications, from low-level database connections (JDBC) to high-level abstractions (Spring Data JPA) + +- JPA: Specification by Oracle (formerly Sun Microsystems) +- Hibernate: Open-source project maintained by Red Hat +- JDBC: Part of the Java Standard Edition (SE) platform +- Spring Data JPA: Part of the Spring Framework ecosystem +- Database vendors: MySQL (Oracle), PostgreSQL (open-source), Oracle Database, Microsoft SQL Server + +## CrudRepository vs. JpaRepository + +[In Spring Boot what is the difference between CrudRepository and JpaRepository in extending a Java repository interface - Stack Overflow](https://stackoverflow.com/questions/72058502/in-spring-boot-what-is-the-difference-between-crudrepository-and-jparepository-i) + +| CrudRepository | JpaRepository | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| CrudRepository does not provide any method for pagination and sorting. | JpaRepository extends PagingAndSortingRepository. It provides all the methods for implementing the pagination. | +| It works as a **marker** interface. | JpaRepository extends both **CrudRepository** and **PagingAndSortingRepository**. | +| It provides CRUD function only. For example **findById(), findAll(),** etc. | It provides some extra methods along with the method of PagingAndSortingRepository and CrudRepository. For example, **flush(), deleteInBatch().** | +| It is used when we do not need the functions provided by JpaRepository and PagingAndSortingRepository. | It is used when we want to implement pagination and sorting functionality in an application. | diff --git a/backend/resources/jpa/jparepository.png b/backend/resources/jpa/jparepository.png new file mode 100644 index 000000000..82c5d07b6 Binary files /dev/null and b/backend/resources/jpa/jparepository.png differ diff --git a/backend/resources/jpa/manytomany-example.png b/backend/resources/jpa/manytomany-example.png new file mode 100644 index 000000000..24fa3179a Binary files /dev/null and b/backend/resources/jpa/manytomany-example.png differ diff --git a/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.md b/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.md new file mode 100644 index 000000000..dd79f2f18 --- /dev/null +++ b/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.md @@ -0,0 +1,74 @@ +# pronunciationApp-v0.2-model + +```mermaid +classDiagram + class User { + +String id + +String usrname + +int age + +String email + +int totalScore + +boolean isActive + } + class Word { + +String id + +String text + +String description + +String sentence + +int difficulty + +boolean isCommon + } + class Pronunciation { + +String id + +String audioName + +int audioSize + +String audioUrl + +String phoneticSpelling + +String speakerGender + +enum type // canonical, recorded + } + class Level { + +String id + +int number + +String name + +int requiredScore + +boolean isBlocked + } + class Category { + +String id + +String categoryName + +String subCategoryName + +String description + +int wordCount + } + class GameProgress { + +String id + +int currentScore + +enum currentStage // stage_01, stage_02 + +Date lastPlayedDate + +int wordsLearned + } + class Stage { + +String id + +String name + +String avatarUrl + +String status + +int progress + +int currentScore + } + class StageWords { + +String id + +enum status // done, pending, fail + +Date lastUpdatedDateTime + } + + + User "1" -- "*" GameProgress : tracks progress + Word "1" -- "1" Pronunciation : has pronunciation + Word "*" -- "*" Category : belongs to group + Word "*" -- "1" Level : has level + GameProgress "1" -- "1" Stage : is at stage + Stage "*" -- "1" Level : has level + Stage "1" -- "*" StageWords : has tracked words + StageWords "1" -- "1" Word : has a word +``` diff --git a/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.png b/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.png new file mode 100644 index 000000000..73ea6f2e9 Binary files /dev/null and b/backend/resources/jpa/model/pronunciationApp-v0.2-model-1.png differ diff --git a/backend/resources/jpa/model/pronunciationApp-v0.2-model-2.md b/backend/resources/jpa/model/pronunciationApp-v0.2-model-2.md new file mode 100644 index 000000000..183a1e8bc --- /dev/null +++ b/backend/resources/jpa/model/pronunciationApp-v0.2-model-2.md @@ -0,0 +1,92 @@ +# pronunciationApp-v0.2-model-2.0 + +## Summary + +The class diagram represents a gamified system for learning words, involving multiple users. + +- The **User** class tracks individual profiles and progress through the **GameProgress** class, which monitors stages and scores. Words are central, belonging to categories (**Category**) and levels (**Level**), with associated pronunciations (**Pronunciation**). + +- Users advance through **Stages**, which track progress and learned words using **StageWords**. Each stage is tied to a level, ensuring structured progression. + +The system also incorporates metadata like word difficulty, phonetic spelling, and stage statuses for personalized learning. + +--- + +### Note + +The context assumes multiple users in a gamified learning environment where the **Word** class is the core entity driving the system. + + + +```mermaid +classDiagram + class User { + +String id + +String usrname + +int age + +String email + +int totalScore + +boolean isActive + } + class Word { + +String id + +String text + +String description + +String sentence + +int difficulty + +boolean isCommon + } + class Pronunciation { + +String id + +String audioName + +int audioSize + +String audioUrl + +String phoneticSpelling + +String speakerGender + +enum type // canonical, recorded + } + class Level { + +String id + +int number + +String name + +int requiredScore + +boolean isBlocked + } + class Category { + +String id + +String categoryName + +String subCategoryName + +String description + +int wordCount + } + class GameProgress { + +String id + +int currentScore + +enum currentStage // stage_01, stage_02 + +Date lastPlayedDate + +int wordsLearned + } + class Stage { + +String id + +String name + +String avatarUrl + +String status + +int progress + +int currentScore + } + class StageWord { + +String id + +enum status // done, pending, fail + +Date lastUpdatedDateTime + } + + + User "1" -- "1" GameProgress : tracks progress + Word "1" -- "*" Pronunciation : has pronunciation + Word "*" -- "1" Category : belongs to group + Word "*" -- "1" Level : has level + GameProgress "1" -- "*" Stage : is at stage + Stage "*" -- "1" Level : has level + Stage "1" -- "*" StageWord : has tracked words + Word "1" -- "*" StageWord : has stageword +``` diff --git a/backend/resources/jpa/onetomany-example.png b/backend/resources/jpa/onetomany-example.png new file mode 100644 index 000000000..e74abd7f4 Binary files /dev/null and b/backend/resources/jpa/onetomany-example.png differ diff --git a/backend/resources/jpa/userAppBorrowBook-2.png b/backend/resources/jpa/userAppBorrowBook-2.png new file mode 100644 index 000000000..daa62829e Binary files /dev/null and b/backend/resources/jpa/userAppBorrowBook-2.png differ diff --git a/backend/resources/mock-data/bash-script/cli-execute-bash.png b/backend/resources/mock-data/bash-script/cli-execute-bash.png new file mode 100644 index 000000000..3af293426 Binary files /dev/null and b/backend/resources/mock-data/bash-script/cli-execute-bash.png differ diff --git a/backend/resources/mock-data/bash-script/data.json b/backend/resources/mock-data/bash-script/data.json new file mode 100644 index 000000000..edfdfa618 --- /dev/null +++ b/backend/resources/mock-data/bash-script/data.json @@ -0,0 +1,318 @@ +[ + { + "id": "8f7d1b9e3a2c5f6e", + "wordName": "aberration", + "definition": "a departure from what is normal, usual, or expected", + "phoneticSpelling": "ˌæbəˈreɪʃən", + "sentence": "The sudden drop in temperature was an aberration for this time of year.", + "level": 3, + "active": true + }, + { + "id": "2c4a6b8d0e1f3g5h", + "wordName": "benevolent", + "definition": "kind, generous, and caring about others", + "phoneticSpelling": "bəˈnevələnt", + "sentence": "The benevolent donor provided funds for the new hospital wing.", + "level": 2, + "active": true + }, + { + "id": "7j9k1l3m5n2o4p6q", + "wordName": "cacophony", + "definition": "a harsh, discordant mixture of sounds", + "phoneticSpelling": "kəˈkɒfəni", + "sentence": "The cacophony of car horns filled the busy street.", + "level": 4, + "active": true + }, + { + "id": "1r3s5t7u9v2w4x6y", + "wordName": "diligent", + "definition": "having or showing care and conscientiousness in one's work or duties", + "phoneticSpelling": "ˈdɪlɪdʒənt", + "sentence": "The diligent student always completed her homework on time.", + "level": 2, + "active": true + }, + { + "id": "8z0a2b4c6d1e3f5g", + "wordName": "ephemeral", + "definition": "lasting for a very short time", + "phoneticSpelling": "ɪˈfemərəl", + "sentence": "The beauty of cherry blossoms is ephemeral, lasting only a few days.", + "level": 3, + "active": true + }, + { + "id": "7h9i1j3k5l2m4n6o", + "wordName": "facetious", + "definition": "treating serious issues with deliberately inappropriate humor", + "phoneticSpelling": "fəˈsiːʃəs", + "sentence": "His facetious remarks about the company's financial troubles were not well-received.", + "level": 4, + "active": true + }, + { + "id": "5p7q9r1s3t5u2v4w", + "wordName": "gregarious", + "definition": "fond of company; sociable", + "phoneticSpelling": "ɡrɪˈɡeəriəs", + "sentence": "The gregarious host made sure all the guests felt welcome at the party.", + "level": 3, + "active": true + }, + { + "id": "6x8y0z2a4b1c3d5e", + "wordName": "harbinger", + "definition": "a person or thing that announces or signals the approach of another", + "phoneticSpelling": "ˈhɑːbɪndʒə", + "sentence": "The robin is often seen as a harbinger of spring.", + "level": 4, + "active": true + }, + { + "id": "9f1g3h5i7j2k4l6m", + "wordName": "incessant", + "definition": "continuing without pause or interruption", + "phoneticSpelling": "ɪnˈsesənt", + "sentence": "The incessant barking of the neighbor's dog kept us awake all night.", + "level": 3, + "active": true + }, + { + "id": "2n4o6p8q0r1s3t5u", + "wordName": "juxtapose", + "definition": "to place or deal with close together for contrasting effect", + "phoneticSpelling": "ˈdʒʌkstəpəʊz", + "sentence": "The artist decided to juxtapose images of war and peace in her latest work.", + "level": 4, + "active": true + }, + { + "id": "7v9w1x3y5z2a4b6c", + "wordName": "kinetic", + "definition": "relating to or resulting from motion", + "phoneticSpelling": "kɪˈnetɪk", + "sentence": "The sculpture's kinetic elements moved gently in the breeze.", + "level": 3, + "active": true + }, + { + "id": "8d0e2f4g6h1i3j5k", + "wordName": "lethargic", + "definition": "sluggish and apathetic", + "phoneticSpelling": "lɪˈθɑːdʒɪk", + "sentence": "The hot weather made everyone feel lethargic and unmotivated.", + "level": 2, + "active": true + }, + { + "id": "1l3m5n7o9p2q4r6s", + "wordName": "mellifluous", + "definition": "sweet or musical; pleasant to hear", + "phoneticSpelling": "məˈlɪfluəs", + "sentence": "The singer's mellifluous voice captivated the audience.", + "level": 4, + "active": true + }, + { + "id": "5t7u9v1w3x5y2z4a", + "wordName": "nefarious", + "definition": "wicked or criminal", + "phoneticSpelling": "nɪˈfeəriəs", + "sentence": "The detective uncovered the CEO's nefarious scheme to embezzle company funds.", + "level": 3, + "active": true + }, + { + "id": "6b8c0d2e4f1g3h5i", + "wordName": "obfuscate", + "definition": "to render obscure, unclear, or unintelligible", + "phoneticSpelling": "ˈɒbfʌskeɪt", + "sentence": "The politician tried to obfuscate the issue by using complex jargon.", + "level": 4, + "active": true + }, + { + "id": "9j1k3l5m7n2o4p6q", + "wordName": "panacea", + "definition": "a solution or remedy for all difficulties or diseases", + "phoneticSpelling": "ˌpænəˈsiːə", + "sentence": "Exercise is not a panacea for all health problems, but it certainly helps.", + "level": 3, + "active": true + }, + { + "id": "2r4s6t8u0v1w3x5y", + "wordName": "quintessential", + "definition": "representing the most perfect or typical example of a quality or class", + "phoneticSpelling": "ˌkwɪntɪˈsenʃəl", + "sentence": "The small town diner was the quintessential American eating establishment.", + "level": 4, + "active": true + }, + { + "id": "7z9a1b3c5d2e4f6g", + "wordName": "resilient", + "definition": "able to withstand or recover quickly from difficult conditions", + "phoneticSpelling": "rɪˈzɪliənt", + "sentence": "The resilient community quickly rebuilt after the natural disaster.", + "level": 3, + "active": true + }, + { + "id": "8h0i2j4k6l1m3n5o", + "wordName": "surreptitious", + "definition": "kept secret, especially because it would not be approved of", + "phoneticSpelling": "ˌsʌrəpˈtɪʃəs", + "sentence": "He made a surreptitious attempt to leave the party without saying goodbye.", + "level": 4, + "active": true + }, + { + "id": "1p3q5r7s9t2u4v6w", + "wordName": "tenacious", + "definition": "tending to keep a firm hold of something; clinging or adhering closely", + "phoneticSpelling": "təˈneɪʃəs", + "sentence": "The tenacious climber refused to give up, even when faced with steep cliffs.", + "level": 3, + "active": true + }, + { + "id": "5x7y9z1a3b5c2d4e", + "wordName": "ubiquitous", + "definition": "present, appearing, or found everywhere", + "phoneticSpelling": "juːˈbɪkwɪtəs", + "sentence": "Smartphones have become ubiquitous in modern society.", + "level": 4, + "active": true + }, + { + "id": "6f8g0h2i4j1k3l5m", + "wordName": "vociferous", + "definition": "expressing or characterized by vehement opinions; loud and forceful", + "phoneticSpelling": "və(ʊ)ˈsɪfərəs", + "sentence": "The vociferous crowd demanded justice for the wrongly accused man.", + "level": 4, + "active": true + }, + { + "id": "9n1o3p5q7r2s4t6u", + "wordName": "whimsical", + "definition": "playfully quaint or fanciful, especially in an appealing and amusing way", + "phoneticSpelling": "ˈwɪmzɪkəl", + "sentence": "The artist's whimsical sculptures brought a smile to everyone's face.", + "level": 3, + "active": true + }, + { + "id": "2v4w6x8y0z1a3b5c", + "wordName": "xenophobia", + "definition": "dislike of or prejudice against people from other countries", + "phoneticSpelling": "ˌzenəˈfəʊbiə", + "sentence": "The politician's xenophobia was evident in his discriminatory policies.", + "level": 4, + "active": true + }, + { + "id": "7d9e1f3g5h2i4j6k", + "wordName": "yearn", + "definition": "to have an intense feeling of longing for something", + "phoneticSpelling": "jɜːn", + "sentence": "After months away, she yearned to return to her hometown.", + "level": 2, + "active": true + }, + { + "id": "8l0m2n4o6p1q3r5s", + "wordName": "zealous", + "definition": "having or showing zeal; enthusiastic and diligent", + "phoneticSpelling": "ˈzeləs", + "sentence": "The zealous volunteer dedicated all her free time to the charity.", + "level": 3, + "active": true + }, + { + "id": "1t3u5v7w9x2y4z6a", + "wordName": "ambivalent", + "definition": "having mixed feelings or contradictory ideas about something or someone", + "phoneticSpelling": "æmˈbɪvələnt", + "sentence": "She felt ambivalent about moving to a new city for her job.", + "level": 3, + "active": true + }, + { + "id": "5b7c9d1e3f5g2h4i", + "wordName": "brevity", + "definition": "concise and exact use of words in writing or speech", + "phoneticSpelling": "ˈbrevɪti", + "sentence": "The speaker's brevity was appreciated by the audience.", + "level": 3, + "active": true + }, + { + "id": "6j8k0l2m4n1o3p5q", + "wordName": "cognizant", + "definition": "having knowledge or awareness", + "phoneticSpelling": "ˈkɒɡnɪzənt", + "sentence": "The manager was cognizant of the team's concerns about the new project.", + "level": 4, + "active": true + }, + { + "id": "9r1s3t5u7v2w4x6y", + "wordName": "dexterous", + "definition": "showing or having skill, especially with the hands", + "phoneticSpelling": "ˈdekstərəs", + "sentence": "The dexterous magician amazed the crowd with his sleight of hand.", + "level": 3, + "active": true + }, + { + "id": "2z4a6b8c0d1e3f5g", + "wordName": "eloquent", + "definition": "fluent or persuasive in speaking or writing", + "phoneticSpelling": "ˈeləkwənt", + "sentence": "Her eloquent speech moved the entire audience to tears.", + "level": 3, + "active": true + }, + { + "id": "7h9i1j3k5l2m4n6o", + "wordName": "fortuitous", + "definition": "happening by chance rather than intention", + "phoneticSpelling": "fɔːˈtjuːɪtəs", + "sentence": "Their meeting at the airport was entirely fortuitous.", + "level": 4, + "active": true + }, + { + "id": "8p0q2r4s6t1u3v5w", + "wordName": "garrulous", + "definition": "excessively talkative, especially on trivial matters", + "phoneticSpelling": "ˈɡærʊləs", + "sentence": "The garrulous passenger talked throughout the entire flight.", + "level": 4, + "active": true + }, + { + "id": "1x3y5z7a9b2c4d6e", + "wordName": "haphazard", + "definition": "lacking any obvious principle of organization", + "phoneticSpelling": "hæpˈhæzəd", + "sentence": "The room was in a haphazard state, with clothes and books strewn everywhere.", + "level": 3, + "active": true + }, + { + "id": "5f7g9h1i3j5k2l4m", + "wordName": "iconoclast", + "definition": "a person who attacks or criticizes cherished beliefs or institutions", + "phoneticSpelling": "aɪˈkɒnəklæst", + "sentence": "The young artist was seen as an iconoclast in the conservative art world.", + "level": 4, + "active": true + } +] + diff --git a/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png b/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png new file mode 100644 index 000000000..9587257d3 Binary files /dev/null and b/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png differ diff --git a/backend/resources/mock-data/bash-script/import_words.sh b/backend/resources/mock-data/bash-script/import_words.sh new file mode 100755 index 000000000..568b90fdd --- /dev/null +++ b/backend/resources/mock-data/bash-script/import_words.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Check if jq is installed +if ! command -v jq &> /dev/null +then + echo "jq is required but not installed. Please install jq and try again." + exit 1 +fi + +# Read the JSON file and process each word object +jq -c '.[]' data.json | while read -r word; do + # Send POST request for each word + curl -X POST \ + -H "Content-Type: application/json" \ + -d "$word" \ + http://localhost:8080/api/words/createWord + + echo -e "\nWord processed" + echo "----------" + + # Optional: Add a small delay between requests to avoid overwhelming the server + sleep 0.5 +done + +echo -e "\nAll words have been posted" + + diff --git a/backend/resources/mock-data/faker-java/java-faker.md b/backend/resources/mock-data/faker-java/java-faker.md new file mode 100644 index 000000000..0f7562bee --- /dev/null +++ b/backend/resources/mock-data/faker-java/java-faker.md @@ -0,0 +1,98 @@ +# Java Faker + +> **Java Faker is a tool that creates realistic-looking fake data**, including names, addresses, phone numbers, and much more. + +- [GitHub - DiUS/java-faker: Brings the popular ruby faker gem to Java](https://github.com/DiUS/java-faker) + +It’s useful for: + +1. **Populating** databases with test data +2. Creating **mock objects for unit testing** +3. Generating sample data for applications +4. Prototyping user interfaces + +The library provides a wide range of pre-defined categories (like name, address, phone number) and methods to generate fake data within those categories. It’s easy to use and can generate data in multiple languages and locales. + +For example, you can create a Faker instance and generate fake data like this: + +```java +Faker faker = new Faker(); + // Generates a random full name +String name = faker.name().fullName(); +// Generates a random email address +String email = faker.internet().emailAddress(); +``` + +Dependency for maven: + +```xml + + com.github.javafaker + javafaker + 1.0.2 + +``` + +## Example #1 + +- [CustomerDataLoader.java at master · AlbertProfe/restaurantManager · GitHub](https://github.com/AlbertProfe/restaurantManager/blob/master/src/main/java/dev/example/restaurantManager/utilities/CustomerDataLoader.java) + +- [DataLoader.java at master · AlbertProfe/restaurantManager · GitHub](https://github.com/AlbertProfe/restaurantManager/blob/master/src/main/java/dev/example/restaurantManager/utilities/DataLoader.java) + +In this example, it's used to create fake customer data for a restaurant management system. Here's how it works: + +1. The `CustomerDataLoader` class is annotated with `@Component`, making it a Spring-managed bean[3]. + +2. It uses `@Autowired` to inject the `CustomerRepository`, which will be used to save the generated data[3]. + +3. The `createFakeCustomers()` method first checks if the database is empty: + +```java +if (customerRepository.count() == 0) { + // Generate fake data +} +``` + +4. A new `Faker` instance is created with the US locale: + +```java +Faker faker = new Faker(new Locale("en-US")); +``` + +5. The method then generates 50 fake customers in a loop: + +```java +for (int i = 0; i < qty; i++) { + Customer customer = new Customer( + UUID.randomUUID().toString(), + faker.name().fullName(), + faker.internet().emailAddress(), + faker.phoneNumber().cellPhone(), + faker.random().nextInt(18, 130), + faker.random().nextBoolean(), + faker.random().nextBoolean() + ); + customerRepository.save(customer); +} +``` + +6. For each customer, Java Faker generates: + + - A random full name using `faker.name().fullName()` + - A random email address using `faker.internet().emailAddress()` + - A random cell phone number using `faker.phoneNumber().cellPhone()` + - A random age between 18 and 130 using `faker.random().nextInt(18, 130)` + - Two random boolean values using `faker.random().nextBoolean()` + +7. Each generated customer is saved to the database using `customerRepository.save(customer)`[3]. + +This approach allows the application to populate the database with realistic-looking customer data for testing or development purposes, without the need for manual data entry. + +Citations: +[1] https://github.com/AlbertProfe/restaurantManager/tree/master +[2] https://albertprofe.dev/springboot/sblab8-1.html +[3] https://albertprofe.dev/springboot/sblab8-3.html + +--- + +Answer from Perplexity: pplx.ai/share diff --git a/backend/resources/mock-data/import-csv/import-csv.md b/backend/resources/mock-data/import-csv/import-csv.md new file mode 100644 index 000000000..ff13c8d9d --- /dev/null +++ b/backend/resources/mock-data/import-csv/import-csv.md @@ -0,0 +1,32 @@ +# Import CSV to H2 + +> To test the H2 database CSV import functionality with a basic CSV file for a UserApp object, follow these steps: + +1. Create a CSV file named `users.csv` with the following content: + +``` +id,username,email,created_at +1,john_doe,john@example.com,2025-01-24 20:00:00 +2,jane_smith,jane@example.com,2025-01-24 20:15:00 +3,bob_johnson,bob@example.com,2025-01-24 20:30:00 +``` + +2. Save this file in a location accessible to your H2 database, for example, `/path/to/users.csv`. + +3. Connect to your H2 database and execute the following SQL command: + +```sql +CREATE TABLE UserApp AS SELECT * FROM CSVREAD('/path/to/users.csv'); +``` + +This command will create a new table named `UserApp` with columns matching the CSV file structure. + +4. To verify the import, you can run a SELECT query: + +```sql +SELECT * FROM UserApp; +``` + +This should display the imported data from the CSV file. + +> **Note** that the H2 database will automatically infer the column types based on the data in the CSV file[2]. In this case, `id` will likely be treated as an INTEGER, while `username` and `email` will be VARCHAR, and `created_at` will be TIMESTAMP. diff --git a/backend/resources/mock-data/import-csv/userApp-from-csv.png b/backend/resources/mock-data/import-csv/userApp-from-csv.png new file mode 100644 index 000000000..163c5bd77 Binary files /dev/null and b/backend/resources/mock-data/import-csv/userApp-from-csv.png differ diff --git a/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png b/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png new file mode 100644 index 000000000..7b92d9239 Binary files /dev/null and b/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png differ diff --git a/backend/resources/mock-data/import-csv/users.csv b/backend/resources/mock-data/import-csv/users.csv new file mode 100644 index 000000000..d4ca3dc38 --- /dev/null +++ b/backend/resources/mock-data/import-csv/users.csv @@ -0,0 +1,4 @@ +id,username,email,created_at +1,john_doe,john@example.com,2025-01-24 20:00:00 +2,jane_smith,jane@example.com,2025-01-24 20:15:00 +3,bob_johnson,bob@example.com,2025-01-24 20:30:00 diff --git a/backend/resources/mock-data/mockServer-postman/mock-serverPostman.md b/backend/resources/mock-data/mockServer-postman/mock-serverPostman.md new file mode 100644 index 000000000..62829a7a2 --- /dev/null +++ b/backend/resources/mock-data/mockServer-postman/mock-serverPostman.md @@ -0,0 +1,49 @@ +# How to create a mock server in Postman from a collection? + +> Mock servers simulate real API behavior and are useful for testing and development without relying on live APIs. + +## Reference + +- https://86c6ea23-f1a9-4a53-85a9-9c9869d64809.mock.pstmn.io/api/words/ + +- [Configure and use a Postman mock server | Postman Docs](https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/) + +## Step-by-step + + + +To create a mock server in Postman from a collection, follow these steps: + +1. **Select the Collection**: + + - In the Postman sidebar, go to **Collections**. + - Find the collection you want to mock, click **View more actions** (three dots), and select **Mock Collection**. + +2. **Configure Mock Server Details**: + + - Provide a name for your mock server. + - (Optional) Choose an environment to use environment variables with your mock server. + - Decide whether to make the mock server private or public. Private servers require an API key in the request header. + - Optionally, simulate a fixed network delay by specifying a response delay. + +3. **Create the Mock Server**: + + - Click **Create Mock Server** to finalize the setup. + - Postman will display the mock server URL, which can be used in requests. + +4. **Add Examples to Requests**: + + - Ensure that each request in your collection has at least one saved example. Postman uses these examples to generate responses for mock requests. + +5. **Use the Mock Server**: + + - Copy the mock server URL and use it in your API requests. + - If the server is private, include your Postman API key as an `x-api-key` header. + +6. **Edit or Delete Mock Servers**: + + - To edit, go to **Mock Servers** in the sidebar, select the desired server, and click **Edit Configuration**. + - To delete, click **View more actions** next to the server name and select **Delete**. + +Citations: +[1] https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-1.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-1.png new file mode 100644 index 000000000..23c7bb50c Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-1.png differ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-2.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-2.png new file mode 100644 index 000000000..e925ef98e Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-2.png differ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-3.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-3.png new file mode 100644 index 000000000..80a3d7bca Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-3.png differ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-4.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-4.png new file mode 100644 index 000000000..afa299449 Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-4.png differ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-5.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-5.png new file mode 100644 index 000000000..09d0cbe06 Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-5.png differ diff --git a/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-6.png b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-6.png new file mode 100644 index 000000000..00cef8081 Binary files /dev/null and b/backend/resources/mock-data/mockServer-postman/mockServer_Postman_from_collection-6.png differ diff --git a/backend/resources/mock-data/sql-insert/data.sql b/backend/resources/mock-data/sql-insert/data.sql new file mode 100644 index 000000000..85f73ed21 --- /dev/null +++ b/backend/resources/mock-data/sql-insert/data.sql @@ -0,0 +1,84 @@ +INSERT INTO book (id, title, author, isbn, pages_qty, available, publication_date) VALUES +('B001', 'To Kill a Mockingbird', 'Harper Lee', '9780446310789', 281, true, '1960-07-11'), +('B002', '1984', 'George Orwell', '9780451524935', 328, true, '1949-06-08'), +('B003', 'Pride and Prejudice', 'Jane Austen', '9780141439518', 432, true, '1813-01-28'), +('B004', 'The Great Gatsby', 'F. Scott Fitzgerald', '9780743273565', 180, true, '1925-04-10'), +('B005', 'One Hundred Years of Solitude', 'Gabriel García Márquez', '9780060883287', 417, true, '1967-05-30'), +('B006', 'The Catcher in the Rye', 'J.D. Salinger', '9780316769174', 234, true, '1951-07-16'), +('B007', 'Moby-Dick', 'Herman Melville', '9780142437247', 720, true, '1851-10-18'), +('B008', 'The Lord of the Rings', 'J.R.R. Tolkien', '9780618640157', 1178, true, '1954-07-29'), +('B009', 'The Hobbit', 'J.R.R. Tolkien', '9780547928227', 366, true, '1937-09-21'), +('B010', 'Brave New World', 'Aldous Huxley', '9780060850524', 311, true, '1932-01-01'), +('B011', 'The Diary of a Young Girl', 'Anne Frank', '9780553296983', 283, true, '1947-06-25'), +('B012', 'The Little Prince', 'Antoine de Saint-Exupéry', '9780156012195', 96, true, '1943-04-06'), +('B013', 'The Alchemist', 'Paulo Coelho', '9780062315007', 208, true, '1988-01-01'), +('B014', 'Harry Potter and the Philosopher''s Stone', 'J.K. Rowling', '9780747532743', 223, true, '1997-06-26'), +('B015', 'The Da Vinci Code', 'Dan Brown', '9780307474278', 454, true, '2003-03-18'), +('B016', 'The Hunger Games', 'Suzanne Collins', '9780439023481', 374, true, '2008-09-14'), +('B017', 'The Girl with the Dragon Tattoo', 'Stieg Larsson', '9780307454546', 672, true, '2005-08-01'), +('B018', 'The Fault in Our Stars', 'John Green', '9780525478812', 313, true, '2012-01-10'), +('B019', 'The Kite Runner', 'Khaled Hosseini', '9781594631931', 371, true, '2003-05-29'), +('B020', 'The Help', 'Kathryn Stockett', '9780425232200', 451, true, '2009-02-10'); +INSERT INTO user_app (id, user_app_name, email, password, age, address, is_archived, dob) VALUES +('U001', 'John Doe', 'john.doe@example.com', 'password123', 30, '123 Main St, Anytown, USA', false, '1995-05-15'), +('U002', 'Jane Smith', 'jane.smith@example.com', 'securepass456', 28, '456 Elm St, Somewhere, USA', false, '1997-08-22'), +('U003', 'Mike Johnson', 'mike.johnson@example.com', 'mikepass789', 35, '789 Oak Ave, Nowhere, USA', false, '1990-03-10'), +('U004', 'Emily Brown', 'emily.brown@example.com', 'emilyb2023', 26, '101 Pine Rd, Elsewhere, USA', false, '1999-11-30'), +('U005', 'David Lee', 'david.lee@example.com', 'leepass321', 40, '202 Cedar Ln, Anyplace, USA', false, '1985-07-18'), +('U006', 'Sarah Wilson', 'sarah.wilson@example.com', 'wilsonpass654', 32, '303 Birch Dr, Somewhere, USA', false, '1993-01-25'), +('U007', 'Tom Anderson', 'tom.anderson@example.com', 'andersonpass987', 45, '404 Maple Ave, Anywhere, USA', false, '1980-09-05'), +('U008', 'Lisa Taylor', 'lisa.taylor@example.com', 'taylorpass246', 29, '505 Spruce St, Someplace, USA', false, '1996-04-12'), +('U009', 'Chris Martin', 'chris.martin@example.com', 'martinpass135', 38, '606 Fir Ln, Otherplace, USA', false, '1987-12-08'), +('U010', 'Amanda Clark', 'amanda.clark@example.com', 'clarkpass579', 33, '707 Ash Rd, Thisplace, USA', false, '1992-06-20'), +('U011', 'Robert White', 'robert.white@example.com', 'whitepass864', 42, '808 Walnut St, Thatplace, USA', false, '1983-02-14'), +('U012', 'Jennifer Green', 'jennifer.green@example.com', 'greenpass753', 31, '909 Chestnut Ave, Hereplace, USA', false, '1994-10-07'), +('U013', 'Daniel Brown', 'daniel.brown@example.com', 'brownpass951', 36, '111 Sycamore Ln, Thereplace, USA', false, '1989-08-29'), +('U014', 'Michelle Lee', 'michelle.lee@example.com', 'leepass357', 27, '222 Poplar Rd, Whereplace, USA', false, '1998-03-17'), +('U015', 'Kevin Davis', 'kevin.davis@example.com', 'davispass159', 39, '333 Willow Dr, Howplace, USA', false, '1986-11-23'), +('U016', 'Laura Wilson', 'laura.wilson@example.com', 'wilsonpass852', 34, '444 Beech St, Whyplace, USA', false, '1991-05-09'), +('U017', 'Mark Thompson', 'mark.thompson@example.com', 'thompsonpass753', 41, '555 Hickory Ave, Whenplace, USA', false, '1984-01-31'), +('U018', 'Sophia Rodriguez', 'sophia.rodriguez@example.com', 'rodriguezpass951', 25, '666 Elm Ln, Whoplace, USA', false, '2000-09-14'), +('U019', 'William Chen', 'william.chen@example.com', 'chenpass357', 37, '777 Oak Rd, Whatplace, USA', false, '1988-07-26'), +('U020', 'Olivia Kim', 'olivia.kim@example.com', 'kimpass159', 30, '888 Pine Dr, Whichplace, USA', false, '1995-12-03'); +INSERT INTO borrow (id, borrow_date, return_date, is_returned, points, user_id, book_id) VALUES +('BR001', '2025-02-15', '2025-03-15', true, 10, 'U001', 'B001'), +('BR002', '2025-02-16', '2025-03-16', false, 5, 'U002', 'B002'), +('BR003', '2025-02-17', '2025-03-17', true, 15, 'U003', 'B003'), +('BR004', '2025-02-18', '2025-03-18', false, 8, 'U004', 'B004'), +('BR005', '2025-02-19', '2025-03-19', true, 12, 'U005', 'B005'), +('BR006', '2025-02-20', '2025-03-20', false, 6, 'U006', 'B006'), +('BR007', '2025-02-21', '2025-03-21', true, 20, 'U007', 'B007'), +('BR008', '2025-02-22', '2025-03-22', false, 7, 'U008', 'B008'), +('BR009', '2025-02-23', '2025-03-23', true, 18, 'U009', 'B009'), +('BR010', '2025-02-24', '2025-03-24', false, 9, 'U010', 'B010'), +('BR011', '2025-02-25', '2025-03-25', true, 14, 'U011', 'B011'), +('BR012', '2025-02-26', '2025-03-26', false, 11, 'U012', 'B012'), +('BR013', '2025-02-27', '2025-03-27', true, 16, 'U013', 'B013'), +('BR014', '2025-02-28', '2025-03-28', false, 8, 'U014', 'B014'), +('BR015', '2025-03-01', '2025-03-31', true, 13, 'U015', 'B015'), +('BR016', '2025-03-02', '2025-04-01', false, 7, 'U016', 'B016'), +('BR017', '2025-03-03', '2025-04-02', true, 19, 'U017', 'B017'), +('BR018', '2025-03-04', '2025-04-03', false, 10, 'U018', 'B018'), +('BR019', '2025-03-05', '2025-04-04', true, 17, 'U019', 'B019'), +('BR020', '2025-03-06', '2025-04-05', false, 9, 'U020', 'B020'); +('BR021', '2025-03-07', '2025-04-06', false, 8, 'U001', 'B005'), +('BR022', '2025-03-08', '2025-04-07', true, 12, 'U001', 'B009'), +('BR023', '2025-03-09', '2025-04-08', false, 7, 'U001', 'B013'), +('BR024', '2025-03-10', '2025-04-09', true, 15, 'U001', 'B017'), +('BR025', '2025-03-11', '2025-04-10', false, 9, 'U002', 'B006'), +('BR026', '2025-03-12', '2025-04-11', true, 11, 'U002', 'B010'), +('BR027', '2025-03-13', '2025-04-12', false, 6, 'U002', 'B014'), +('BR028', '2025-03-14', '2025-04-13', true, 14, 'U002', 'B018'), +('BR029', '2025-03-15', '2025-04-14', false, 10, 'U003', 'B007'), +('BR030', '2025-03-16', '2025-04-15', true, 13, 'U003', 'B011'), +('BR031', '2025-03-17', '2025-04-16', false, 8, 'U003', 'B015'), +('BR032', '2025-03-18', '2025-04-17', true, 16, 'U003', 'B019'), +('BR033', '2025-03-19', '2025-04-18', false, 7, 'U004', 'B008'), +('BR034', '2025-03-20', '2025-04-19', true, 12, 'U004', 'B012'), +('BR035', '2025-03-21', '2025-04-20', false, 9, 'U004', 'B016'), +('BR036', '2025-03-22', '2025-04-21', true, 15, 'U004', 'B020'); + + + + + diff --git a/backend/resources/mock-data/sql-insert/sql-insert.md b/backend/resources/mock-data/sql-insert/sql-insert.md new file mode 100644 index 000000000..4015963c9 --- /dev/null +++ b/backend/resources/mock-data/sql-insert/sql-insert.md @@ -0,0 +1,149 @@ +# Insert data + +## Links + +- [mock-data at backend-spring-boot](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/resources/mock-data) +- [GitHub - AlbertProfe/userBorrowBook](https://github.com/AlbertProfe/userBorrowBook) + +## Code + +#### book + +This `data.sql` file with INSERT statements to create 20 books for the `Book` `entity`: + +```sql +INSERT INTO book (id, title, author, isbn, pages_qty, available, publication_date) VALUES +('B001', 'To Kill a Mockingbird', 'Harper Lee', '9780446310789', 281, true, '1960-07-11'), +('B002', '1984', 'George Orwell', '9780451524935', 328, true, '1949-06-08'), +('B003', 'Pride and Prejudice', 'Jane Austen', '9780141439518', 432, true, '1813-01-28'), +('B004', 'The Great Gatsby', 'F. Scott Fitzgerald', '9780743273565', 180, true, '1925-04-10'), +('B005', 'One Hundred Years of Solitude', 'Gabriel García Márquez', '9780060883287', 417, true, '1967-05-30'), +('B006', 'The Catcher in the Rye', 'J.D. Salinger', '9780316769174', 234, true, '1951-07-16'), +('B007', 'Moby-Dick', 'Herman Melville', '9780142437247', 720, true, '1851-10-18'), +('B008', 'The Lord of the Rings', 'J.R.R. Tolkien', '9780618640157', 1178, true, '1954-07-29'), +('B009', 'The Hobbit', 'J.R.R. Tolkien', '9780547928227', 366, true, '1937-09-21'), +('B010', 'Brave New World', 'Aldous Huxley', '9780060850524', 311, true, '1932-01-01'), +('B011', 'The Diary of a Young Girl', 'Anne Frank', '9780553296983', 283, true, '1947-06-25'), +('B012', 'The Little Prince', 'Antoine de Saint-Exupéry', '9780156012195', 96, true, '1943-04-06'), +('B013', 'The Alchemist', 'Paulo Coelho', '9780062315007', 208, true, '1988-01-01'), +('B014', 'Harry Potter and the Philosopher''s Stone', 'J.K. Rowling', '9780747532743', 223, true, '1997-06-26'), +('B015', 'The Da Vinci Code', 'Dan Brown', '9780307474278', 454, true, '2003-03-18'), +('B016', 'The Hunger Games', 'Suzanne Collins', '9780439023481', 374, true, '2008-09-14'), +('B017', 'The Girl with the Dragon Tattoo', 'Stieg Larsson', '9780307454546', 672, true, '2005-08-01'), +('B018', 'The Fault in Our Stars', 'John Green', '9780525478812', 313, true, '2012-01-10'), +('B019', 'The Kite Runner', 'Khaled Hosseini', '9781594631931', 371, true, '2003-05-29'), +('B020', 'The Help', 'Kathryn Stockett', '9780425232200', 451, true, '2009-02-10'); +``` + +This SQL script creates 20 books with unique UUIDs/strings, varied titles, authors, ISBNs, page counts, and publication dates. + +All books are set as available (true). + + We can adjust the values as needed for your specific use case. + +To use this in your Spring Boot application: + +1. Place this SQL in a file named `data.sql` in your `src/main/resources` directory. + +2. Ensure your `application.properties` or `application.yml` file has the following property: + +```properties +spring.jpa.defer-datasource-initialization=true +``` + +This will tell Spring Boot to execute the `data.sql` file after the schema has been created. + +> Remember that if you're using an in-memory database like H2, this data will be inserted every time your application starts. +> +> For persistent databases, you might want to run this script manually or use a migration tool like Flyway or Liquibase for more control over when and how data is inserted. + +#### user + +```sql +INSERT INTO user_app (id, user_app_name, email, password, age, address, is_archived, dob) VALUES +('U001', 'John Doe', 'john.doe@example.com', 'password123', 30, '123 Main St, Anytown, USA', false, '1995-05-15'), +('U002', 'Jane Smith', 'jane.smith@example.com', 'securepass456', 28, '456 Elm St, Somewhere, USA', false, '1997-08-22'), +('U003', 'Mike Johnson', 'mike.johnson@example.com', 'mikepass789', 35, '789 Oak Ave, Nowhere, USA', false, '1990-03-10'), +('U004', 'Emily Brown', 'emily.brown@example.com', 'emilyb2023', 26, '101 Pine Rd, Elsewhere, USA', false, '1999-11-30'), +('U005', 'David Lee', 'david.lee@example.com', 'leepass321', 40, '202 Cedar Ln, Anyplace, USA', false, '1985-07-18'), +('U006', 'Sarah Wilson', 'sarah.wilson@example.com', 'wilsonpass654', 32, '303 Birch Dr, Somewhere, USA', false, '1993-01-25'), +('U007', 'Tom Anderson', 'tom.anderson@example.com', 'andersonpass987', 45, '404 Maple Ave, Anywhere, USA', false, '1980-09-05'), +('U008', 'Lisa Taylor', 'lisa.taylor@example.com', 'taylorpass246', 29, '505 Spruce St, Someplace, USA', false, '1996-04-12'), +('U009', 'Chris Martin', 'chris.martin@example.com', 'martinpass135', 38, '606 Fir Ln, Otherplace, USA', false, '1987-12-08'), +('U010', 'Amanda Clark', 'amanda.clark@example.com', 'clarkpass579', 33, '707 Ash Rd, Thisplace, USA', false, '1992-06-20'), +('U011', 'Robert White', 'robert.white@example.com', 'whitepass864', 42, '808 Walnut St, Thatplace, USA', false, '1983-02-14'), +('U012', 'Jennifer Green', 'jennifer.green@example.com', 'greenpass753', 31, '909 Chestnut Ave, Hereplace, USA', false, '1994-10-07'), +('U013', 'Daniel Brown', 'daniel.brown@example.com', 'brownpass951', 36, '111 Sycamore Ln, Thereplace, USA', false, '1989-08-29'), +('U014', 'Michelle Lee', 'michelle.lee@example.com', 'leepass357', 27, '222 Poplar Rd, Whereplace, USA', false, '1998-03-17'), +('U015', 'Kevin Davis', 'kevin.davis@example.com', 'davispass159', 39, '333 Willow Dr, Howplace, USA', false, '1986-11-23'), +('U016', 'Laura Wilson', 'laura.wilson@example.com', 'wilsonpass852', 34, '444 Beech St, Whyplace, USA', false, '1991-05-09'), +('U017', 'Mark Thompson', 'mark.thompson@example.com', 'thompsonpass753', 41, '555 Hickory Ave, Whenplace, USA', false, '1984-01-31'), +('U018', 'Sophia Rodriguez', 'sophia.rodriguez@example.com', 'rodriguezpass951', 25, '666 Elm Ln, Whoplace, USA', false, '2000-09-14'), +('U019', 'William Chen', 'william.chen@example.com', 'chenpass357', 37, '777 Oak Rd, Whatplace, USA', false, '1988-07-26'), +('U020', 'Olivia Kim', 'olivia.kim@example.com', 'kimpass159', 30, '888 Pine Dr, Whichplace, USA', false, '1995-12-03'); +``` + +### borrow + +Now, let's assign books and users with borrow: this is a `data.sql` insert statement to create 20 borrow records based on the `Borrow` entity: + +```sql +INSERT INTO borrow (id, borrow_date, return_date, is_returned, points, user_id, book_id) VALUES +('BR001', '2025-02-15', '2025-03-15', true, 10, 'U001', 'B001'), +('BR002', '2025-02-16', '2025-03-16', false, 5, 'U002', 'B002'), +('BR003', '2025-02-17', '2025-03-17', true, 15, 'U003', 'B003'), +('BR004', '2025-02-18', '2025-03-18', false, 8, 'U004', 'B004'), +('BR005', '2025-02-19', '2025-03-19', true, 12, 'U005', 'B005'), +('BR006', '2025-02-20', '2025-03-20', false, 6, 'U006', 'B006'), +('BR007', '2025-02-21', '2025-03-21', true, 20, 'U007', 'B007'), +('BR008', '2025-02-22', '2025-03-22', false, 7, 'U008', 'B008'), +('BR009', '2025-02-23', '2025-03-23', true, 18, 'U009', 'B009'), +('BR010', '2025-02-24', '2025-03-24', false, 9, 'U010', 'B010'), +('BR011', '2025-02-25', '2025-03-25', true, 14, 'U011', 'B011'), +('BR012', '2025-02-26', '2025-03-26', false, 11, 'U012', 'B012'), +('BR013', '2025-02-27', '2025-03-27', true, 16, 'U013', 'B013'), +('BR014', '2025-02-28', '2025-03-28', false, 8, 'U014', 'B014'), +('BR015', '2025-03-01', '2025-03-31', true, 13, 'U015', 'B015'), +('BR016', '2025-03-02', '2025-04-01', false, 7, 'U016', 'B016'), +('BR017', '2025-03-03', '2025-04-02', true, 19, 'U017', 'B017'), +('BR018', '2025-03-04', '2025-04-03', false, 10, 'U018', 'B018'), +('BR019', '2025-03-05', '2025-04-04', true, 17, 'U019', 'B019'), +('BR020', '2025-03-06', '2025-04-05', false, 9, 'U020', 'B020'); + +-- Additional borrows for U001 +('BR021', '2025-03-07', '2025-04-06', false, 8, 'U001', 'B005'), +('BR022', '2025-03-08', '2025-04-07', true, 12, 'U001', 'B009'), +('BR023', '2025-03-09', '2025-04-08', false, 7, 'U001', 'B013'), +('BR024', '2025-03-10', '2025-04-09', true, 15, 'U001', 'B017'), + +-- Additional borrows for U002 +('BR025', '2025-03-11', '2025-04-10', false, 9, 'U002', 'B006'), +('BR026', '2025-03-12', '2025-04-11', true, 11, 'U002', 'B010'), +('BR027', '2025-03-13', '2025-04-12', false, 6, 'U002', 'B014'), +('BR028', '2025-03-14', '2025-04-13', true, 14, 'U002', 'B018'), + +-- Additional borrows for U003 +('BR029', '2025-03-15', '2025-04-14', false, 10, 'U003', 'B007'), +('BR030', '2025-03-16', '2025-04-15', true, 13, 'U003', 'B011'), +('BR031', '2025-03-17', '2025-04-16', false, 8, 'U003', 'B015'), +('BR032', '2025-03-18', '2025-04-17', true, 16, 'U003', 'B019'), + +-- Additional borrows for U004 +('BR033', '2025-03-19', '2025-04-18', false, 7, 'U004', 'B008'), +('BR034', '2025-03-20', '2025-04-19', true, 12, 'U004', 'B012'), +('BR035', '2025-03-21', '2025-04-20', false, 9, 'U004', 'B016'), +('BR036', '2025-03-22', '2025-04-21', true, 15, 'U004', 'B020'); +``` + +This `SQL` script creates 20 borrow records with string IDs (BR001 to BR020). Each record includes: + +1. A `borrow` date +2. A return date (set to 30 days after the borrow date) +3. A `boolean` indicating whether the book has been returned +4. A `number` of points +5. A user ID (matching the user IDs from the previous insert) +6. A book ID (matching the book IDs from the previous insert) + +Note that we've used future dates (2025) for the borrow and return dates to ensure they're not in the past when you run this script. You may want to adjust these dates based on your specific requirements. + +Remember to place this SQL in your `data.sql` file along with the previous inserts for users and books. : diff --git a/backend/resources/pronunciationApp-v0.1.md b/backend/resources/pronunciationApp-v0.1.md new file mode 100644 index 000000000..62dbb8fcd --- /dev/null +++ b/backend/resources/pronunciationApp-v0.1.md @@ -0,0 +1,390 @@ +# PronunciationApp Backend v0.1 + +## Project + +- pronunciationApp Backend **GitHub** [code](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/pronunciationAppBack) + +- pronunciatoinApp Backend [resources](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/resources) **documentation** + +### Project Structure + +A Spring Boot backend application for the `Pronunciation App`, using: + +- H2 Database (local file-based or memory for certain purposes) +- `Spring Data JPA` +- REST Controller +- Service Layer +- Entity Mapping: `@Entity` + +### Dependencies + +Add these to your `pom.xml`: + +```xml + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + com.h2database + h2 + runtime + + +``` + +### Entity + +`Word.java`: + +```java +package com.pronunciationapp.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Word { + + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + // Constructors + public Word() {} + + public Word(String id, String wordName, String definition, + String phoneticSpelling, String sentence, + boolean isActive, int level) { + this.id = id; + this.wordName = wordName; + this.definition = definition; + this.phoneticSpelling = phoneticSpelling; + this.sentence = sentence; + this.isActive = isActive; + this.level = level; + } + + // Getters and Setters + // ... (generate these for all fields) +} +``` + +### Repository + +`WordRepository.java`: + +```java +package com.pronunciationapp.repository; + +import com.pronunciationapp.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface WordRepository extends JpaRepository { + List findByIsActiveTrue(); + List findByLevel(int level); + Word findByWordName(String wordName); +} +``` + +### Service + +`WordService.java`: + +```java +package com.pronunciationapp.service; + +import com.pronunciationapp.model.Word; +import com.pronunciationapp.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Optional getWordById(String id) { + return wordRepository.findById(id); + } + + public Word createWord(Word word) { + if (word.getId() == null) { + word.setId(UUID.randomUUID().toString()); + } + return wordRepository.save(word); + } + + public Word updateWord(String id, Word wordDetails) { + Word word = wordRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Word not found")); + + word.setWordName(wordDetails.getWordName()); + word.setDefinition(wordDetails.getDefinition()); + word.setPhoneticSpelling(wordDetails.getPhoneticSpelling()); + word.setSentence(wordDetails.getSentence()); + word.setActive(wordDetails.isActive()); + word.setLevel(wordDetails.getLevel()); + + return wordRepository.save(word); + } + + public void deleteWord(String id) { + Word word = wordRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Word not found")); + wordRepository.delete(word); + } + + public List getActiveWords() { + return wordRepository.findByIsActiveTrue(); + } + + public List getWordsByLevel(int level) { + return wordRepository.findByLevel(level); + } +} +``` + +### REST Controller + +`WordController.java`: + +```java +package com.pronunciationapp.controller; + +import com.pronunciationapp.model.Word; +import com.pronunciationapp.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/words") +public class WordController { + + @Autowired + private WordService wordService; + + @GetMapping + public List getAllWords() { + return wordService.getAllWords(); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable String id) { + return wordService.getWordById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public Word createWord(@RequestBody Word word) { + return wordService.createWord(word); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord( + @PathVariable String id, + @RequestBody Word wordDetails + ) { + Word updatedWord = wordService.updateWord(id, wordDetails); + return ResponseEntity.ok(updatedWord); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable String id) { + wordService.deleteWord(id); + return ResponseEntity.ok().build(); + } + + @GetMapping("/active") + public List getActiveWords() { + return wordService.getActiveWords(); + } + + @GetMapping("/level/{level}") + public List getWordsByLevel(@PathVariable int level) { + return wordService.getWordsByLevel(level); + } +} +``` + +### Application Properties + +`application.properties`: + +```properties +spring.application.name=pronunciationAppBack + +# H2 DATABASE SERVER +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/pronunciationDB/pronunciationDB.db +spring.datasource.username=albert +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=update + +# Enable H2 web console +spring.h2.console.path=/h2-console +``` + +## Testing with Postman + +Sample JSON for testing: + +```json +{ + "words": [ + { + "id": "8b9248a4e0b64bbccf82e7723a3734279bf9bbc4", + "wordName": "benevolent", + "definition": "Kind and generous", + "phoneticSpelling": "/bɪˈnɛvələnt/", + "sentence": "Her benevolent actions helped many people in need.", + "isActive": true, + "level": 2 + }, + { + "id": "3a7bd3e2a07e8c7e9b6e0d2c1f4a5b8d9c0e3f2", + "wordName": "serendipity", + "definition": "A fortunate discovery", + "phoneticSpelling": "/ˌserənˈdɪpɪti/", + "sentence": "Meeting her was pure serendipity.", + "isActive": true, + "level": 3 + } + ] +} +``` + +### Postman Collection Setup + +Create a new collection for `PronunciationApp` with these CRUD endpoints: + +1. **GET All Words** + + - Method: GET + - URL: `http://localhost:8080/api/words` + - Expected: Returns full list of words + +2. **GET Word by ID** + + - Method: GET + - URL: `http://localhost:8080/api/words/{id}` + - Params: Replace `{id}` with a specific word ID + - Expected: Returns single word details + +3. **POST Create Word** + + - Method: POST + + - URL: `http://localhost:8080/api/words` + + - Body (raw JSON): + + ```json + { + "wordName": "ephemeral", + "definition": "Lasting for a very short time", + "phoneticSpelling": "/ɪˈfɛmərəl/", + "sentence": "The beauty of cherry blossoms is ephemeral.", + "isActive": true, + "level": 2 + } + ``` + + - Expected: Returns created word with auto-generated ID + +4. **PUT Update Word** + + - Method: PUT + + - URL: `http://localhost:8080/api/words/{id}` + + - Params: Replace `{id}` with existing word ID + + - Body (raw JSON): + + ```json + { + "wordName": "ephemeral", + "definition": "Lasting for a very short time", + "phoneticSpelling": "/ɪˈfɛmərəl/", + "sentence": "The beauty of cherry blossoms is ephemeral.", + "isActive": false, + "level": 3 + } + ``` + + - Expected: Returns updated word + +5. **DELETE Word** + + - Method: DELETE + - URL: `http://localhost:8080/api/words/{id}` + - Params: Replace `{id}` with word ID to delete + - Expected: 200 OK response + +### Additional Test Endpoints + +- **GET Active Words**: `http://localhost:8080/api/words/active` +- **GET Words by Level**: `http://localhost:8080/api/words/level/2` + +### Testing Workflow + +1. Import collection in Postman +2. Start Spring Boot application +3. Execute requests in sequence +4. Verify responses match expected results + +### Common Testing Scenarios + +- Test creating words with missing fields +- Validate ID generation +- Check level and active status constraints +- Test boundary conditions (max/min levels) + +## Running the Application + +1. Ensure you have Java 17+ and Maven installed +2. Configure the database path in `application.properties` +3. Run the Spring Boot application +4. Access H2 console at `http://localhost:8080/h2-console` +5. Access API endpoints at `http://localhost:8080/api/words` + +## Notes + +- The application uses a **local** H2 database file +- UUID is used for generating unique word IDs +- Basic CRUD operations are implemented +- Additional **query methods are available in the repository** and service layers diff --git a/backend/resources/pronunciationApp-v0.2.md b/backend/resources/pronunciationApp-v0.2.md new file mode 100644 index 000000000..aaf6a676a --- /dev/null +++ b/backend/resources/pronunciationApp-v0.2.md @@ -0,0 +1,498 @@ +# PronunciationApp Backend v0.2 + +## Refactoring @RestController and adding @Service + +> Implementing a `WordService` and refactoring the `WordController` is a good practice in Spring Boot applications for several reasons. + +Let's implement the `WordService`, refactor the `WordController`, and discuss why this approach is beneficial. + +### Implementing WordService + +```java +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Optional getWordById(String id) { + return Optional.ofNullable(wordRepository.getWordById(id)); + } + + public Word createWord(Word word) { + return wordRepository.save(word); + } + + public Word updateWord(Word word) { + return wordRepository.save(word); + } + + public void deleteWord(String id) { + wordRepository.deleteById(id); + } + + public void deleteAllWords() { + wordRepository.deleteAll(); + } + + public boolean existsById(String id) { + return wordRepository.existsById(id); + } + + public long getWordCount() { + return wordRepository.count(); + } + + // Additional business logic can be added here + public Word getWordByPhoneticSpelling(String pronunciation) { + return wordRepository.getWordByPhoneticSpelling(pronunciation); + } +} +``` + +### Why is this refactoring necessary? + +1. **Separation of Concerns**: The service layer separates business logic from the controller, making the code more modular and easier to maintain. + +2. **Reusability**: Business logic in the service can be reused across different controllers or other services. + +3. **Testability**: It's easier to unit test business logic in a service class than in a controller. + +4. **Scalability**: As the application grows, having a separate service layer makes it easier to manage and scale the codebase. + +5. **Abstraction**: The controller doesn't need to know about the repository implementation, providing better abstraction. + +#### Refactoring WordController + +```java +@RestController +@RequestMapping("/api/words") +public class WordController { + + @Autowired + private WordService wordService; + + /* + @GetMapping("/hello") + public ResponseEntity hello() { + HttpHeaders headers = getCommonHeaders("Hello endpoint"); + return new ResponseEntity<>("hello Emiliano, are you sleeping?", headers, HttpStatus.OK); + } + */ + + @GetMapping + public ResponseEntity> getAllWords() { + List words = wordService.getAllWords(); + HttpHeaders headers = getCommonHeaders("Get all words"); + + return !words.isEmpty() + ? new ResponseEntity<>(words, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable String id) { + Optional word = wordService.getWordById(id); + HttpHeaders headers = getCommonHeaders("Get word by ID"); + + return word.map(value -> new ResponseEntity<>(value, headers, HttpStatus.OK)) + .orElseGet(() -> new ResponseEntity<>(headers, HttpStatus.NOT_FOUND)); + } + + @PostMapping("/createWord") + public ResponseEntity createWord(@RequestBody Word word) { + Word createdWord = wordService.createWord(word); + HttpHeaders headers = getCommonHeaders("Create a new word"); + + return new ResponseEntity<>(createdWord, headers, HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord(@PathVariable String id, @RequestBody Word word) { + Word updatedWord = wordService.updateWord(word); + HttpHeaders headers = getCommonHeaders("Update a word"); + + return new ResponseEntity<>(updatedWord, headers, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable("id") String idToDelete) { + HttpHeaders headers = getCommonHeaders("Delete a word"); + + if (wordService.existsById(idToDelete)) { + wordService.deleteWord(idToDelete); + return new ResponseEntity<>("Word deleted", headers, HttpStatus.OK); + } else { + return new ResponseEntity<>("Word not found", headers, HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping + public ResponseEntity deleteAllWords() { + wordService.deleteAllWords(); + HttpHeaders headers = getCommonHeaders("Delete all words"); + return new ResponseEntity<>("All words deleted", headers, HttpStatus.OK); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + headers.add("word-count", String.valueOf(wordService.getWordCount())); + headers.add("object", "words"); + return headers; + } +} +``` + +## Check health + +Well create a new controller class that incorporates the health check endpoint along with other best practices. + +Here's the new `HealthController` class: + +```java +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.Word; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/health") +public class HealthController { + + @Autowired + private WordService wordService; + + @GetMapping + public ResponseEntity> healthCheck() { + Map healthStatus = new HashMap<>(); + healthStatus.put("status", "UP"); + healthStatus.put("timestamp", new Date()); + healthStatus.put("wordCount", wordService.getWordCount()); + + boolean databaseConnection = checkDatabaseConnection(); + healthStatus.put("database", databaseConnection ? "Connected" : "Disconnected"); + + // Add more health checks as needed + healthStatus.put("memoryUsage", getMemoryUsage()); + healthStatus.put("diskSpace", getDiskSpace()); + + HttpHeaders headers = getCommonHeaders("Health check endpoint"); + HttpStatus status = databaseConnection ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; + + return new ResponseEntity<>(healthStatus, headers, status); + } + + private boolean checkDatabaseConnection() { + try { + wordService.getAllWords(); + return true; + } catch (Exception e) { + return false; + } + } + + private Map getMemoryUsage() { + Runtime runtime = Runtime.getRuntime(); + Map memoryInfo = new HashMap<>(); + memoryInfo.put("total", runtime.totalMemory()); + memoryInfo.put("free", runtime.freeMemory()); + memoryInfo.put("used", runtime.totalMemory() - runtime.freeMemory()); + return memoryInfo; + } + + private Map getDiskSpace() { + java.io.File root = new java.io.File("/"); + Map diskInfo = new HashMap<>(); + diskInfo.put("total", root.getTotalSpace()); + diskInfo.put("free", root.getFreeSpace()); + diskInfo.put("usable", root.getUsableSpace()); + return diskInfo; + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("desc", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "Spring Boot"); + headers.add("version", "1.0.0"); + return headers; + } +} +``` + +This new `HealthController` class offers several improvements: + +1. **Dedicated controller**: It's a separate controller focused on health and monitoring, following the Single Responsibility Principle. + +2. **Comprehensive health check**: The `healthCheck()` method provides a detailed health status, including database connectivity, word count, memory usage, and disk space. + +3. **Modular design**: Different aspects of the health check are separated into methods, making the code more maintainable and easier to extend. + +4. **Resource monitoring**: It includes basic system resource monitoring (memory and disk space), which can be crucial for identifying potential issues. + +5. **Consistent headers**: It uses a `getCommonHeaders()` method similar to the `WordController`, maintaining consistency across the API. + +6. **Appropriate mapping**: The controller uses the `/api/health` endpoint, which is a common convention for health check APIs. + +> This health check endpoint provides a comprehensive overview of your application's health, making it valuable for monitoring and troubleshooting in production environments. + +## @Service business logic examples + +#### Real Use Case #1 + +Let's consider a real use case where the service layer becomes key: + +Imagine you're expanding your pronunciation app to include a feature for generating word lists based on difficulty levels. This involves complex logic that shouldn't be in the controller. + +Add this method to `WordService`: + +```java +public List getWordsByDifficultyLevel(int level) { + List allWords = wordRepository.findAll(); + return allWords.stream() + .filter(word -> calculateDifficulty(word) == level) + .collect(Collectors.toList()); +} + +private int calculateDifficulty(Word word) { + // Complex logic to determine word difficulty + // Based on factors like length, rarity, phonetic complexity, etc. + // This is a simplified example + int difficulty = word.getWord().length(); + difficulty += word.getPhoneticSpelling().length(); + // More factors... + return difficulty / 5; // Normalize to a 1-10 scale +} +``` + +And in `WordController`: + +```java +@GetMapping("/difficulty/{level}") +public ResponseEntity> getWordsByDifficulty(@PathVariable int level) { + List words = wordService.getWordsByDifficultyLevel(level); + HttpHeaders headers = getCommonHeaders("Get words by difficulty"); + + return !words.isEmpty() + ? new ResponseEntity<>(words, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); +} +``` + +In this case, the service layer is key because: + +1. It encapsulates complex business logic (difficulty calculation) that doesn't belong in the controller. +2. This logic can be reused in other parts of the application. +3. It's easier to test and modify the difficulty calculation independently of the controller. +4. If you decide to change how difficulty is calculated or stored (e.g., moving to a database-computed value), you only need to change the service, not the controller. + +> This structure allows your application to grow and adapt to new requirements more easily, demonstrating the importance of a well-structured service layer in a Spring Boot application. + +#### Real Use Case #2 + +Let's consider another real-world use case where the WordService would be essential: implementing a personalized word recommendation system based on a user's learning history and performance. + +Here's how we could implement this feature: + +```java +@Service +public class WordService { + @Autowired + private WordRepository wordRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserProgressRepository userProgressRepository; + + public List getPersonalizedWordRecommendations(String userId, int count) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException("User not found")); + + List userProgress = userProgressRepository.findByUserId(userId); + + Set masteredWords = userProgress.stream() + .filter(progress -> progress.getMasteryLevel() > 0.8) + .map(UserProgress::getWordId) + .collect(Collectors.toSet()); + + List allWords = wordRepository.findAll(); + + return allWords.stream() + .filter(word -> !masteredWords.contains(word.getId())) + .sorted(Comparator.comparingDouble(word -> calculateRelevance(word, user, userProgress))) + .limit(count) + .collect(Collectors.toList()); + } + + private double calculateRelevance(Word word, User user, List userProgress) { + double difficultyScore = calculateDifficulty(word); + double userLevelScore = user.getProficiencyLevel(); + double progressScore = userProgress.stream() + .filter(progress -> progress.getWordId().equals(word.getId())) + .mapToDouble(UserProgress::getMasteryLevel) + .findFirst() + .orElse(0.0); + + // Complex algorithm to determine word relevance based on user's level, + // word difficulty, and user's progress on this word + return (difficultyScore * 0.4) + (userLevelScore * 0.3) + ((1 - progressScore) * 0.3); + } + + private double calculateDifficulty(Word word) { + // Implementation of difficulty calculation + // ... + } +} +``` + +In the controller: + +```java +@RestController +@RequestMapping("/api/words") +public class WordController { + @Autowired + private WordService wordService; + + @GetMapping("/recommendations/{userId}") + public ResponseEntity> getPersonalizedRecommendations( + @PathVariable String userId, + @RequestParam(defaultValue = "10") int count) { + List recommendations = wordService.getPersonalizedWordRecommendations(userId, count); + HttpHeaders headers = getCommonHeaders("Get personalized word recommendations"); + + return !recommendations.isEmpty() + ? new ResponseEntity<>(recommendations, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } +} +``` + +This use case demonstrates why the service layer is key: + +1. Complex Business Logic: The recommendation system involves complex calculations and data processing that shouldn't be in the controller. + +2. Data Integration: It requires integration of data from multiple repositories (words, users, and user progress), which is better handled in a service. + +3. Reusability: The recommendation logic can be reused in other parts of the application, such as generating daily practice sessions or progress reports. + +4. Scalability: As the recommendation algorithm becomes more sophisticated (e.g., incorporating machine learning), the service can be easily extended without affecting the controller. + +5. Testability: The complex recommendation logic can be unit tested independently of the web layer. + +6. Separation of Concerns: The controller remains focused on handling HTTP requests and responses, while the service manages the business logic. + +7. Flexibility: If you decide to change how recommendations are generated (e.g., using a third-party AI service), you only need to modify the service, not the controller. + +> This example showcases how a well-structured service layer can handle complex, data-intensive operations while keeping the controller lean and focused on its primary responsibility of managing HTTP interactions. + +## JpaRepository + +```java +public interface WordRepository extends JpaRepository { +``` + +> JpaRepository is an interface provided by Spring Data JPA that simplifies database operations. +> +> It extends other repository interfaces, offering a complete set of CRUD (Create, Read, Update, Delete) operations, as well as paging and sorting capabilities. + +An interface in Java is a contract that specifies a set of abstract methods that a class must implement. It defines what a class should do, without specifying how it should do it. + +In the case of `WordRepository`, extending` JpaRepository` is a good idea because: + +1. Automatic implementation: Spring Data JPA automatically generates the implementation of the repository interface at runtime, saving developers from writing boilerplate code. + +2. Built-in methods: It provides common database operations like save(), findAll(), and deleteById() out of the box. + +3. Custom queries: You can define custom query methods, as seen with getWordById() and getWordByPhoneticSpelling(), by simply declaring them in the interface. + +4. Type safety: JpaRepository uses generics (), ensuring type safety for the entity (Word) and its ID type (String). + +5. Extensibility: You can easily add more custom methods as your application's requirements grow. + +### Queries + +- [Lab#SB08-3: H2 and API Rest – albertprofe wiki](https://albertprofe.dev/springboot/sblab8-3.html#jpa-query-methods) + +**JPA Derived Query Methods** + +`Spring Data JPA` can automatically create queries based on method names in your repository interface. + +```java +public interface UserRepository + extends JpaRepository { + List findByLastNameAndAge(String lastName, int age); +} +``` + +**@Query Annotation** + +You can use the `@Query` annotation to define custom JPQL queries. + +```java +public interface UserRepository + extends JpaRepository { + @Query("SELECT u FROM User u WHERE u.emailAddress = ?1") + User findByEmailAddress(String emailAddress); +} +``` + +**EntityManager with JPQL** + +For more complex queries, you can use the `EntityManager` directly with JPQL. + +```java +@PersistenceContext +private EntityManager entityManager; + +public List findUsersByAgeRange(int minAge, int maxAge) { + String jpql = "SELECT u FROM User u WHERE u.age BETWEEN :minAge AND :maxAge"; + return entityManager.createQuery(jpql, User.class) + .setParameter("minAge", minAge) + .setParameter("maxAge", maxAge) + .getResultList(); +} +``` + +**Native SQL Queries** + +When you need to use database-specific features, you can write native SQL queries. + +```java +public interface UserRepository extends JpaRepository { + @Query(value = "SELECT * FROM users WHERE status = ?1", nativeQuery = true) + List findUsersByStatus(int status); +} +``` diff --git a/backend/resources/pronunciationApp-v0.3.md b/backend/resources/pronunciationApp-v0.3.md new file mode 100644 index 000000000..5aaff7bcf --- /dev/null +++ b/backend/resources/pronunciationApp-v0.3.md @@ -0,0 +1,569 @@ +# PronunciationApp Backend v0.3 + +## JPA References + +- [Spring Boot: Data & DB – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-data.html) + +- [Spring Boot: JPA & DI – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa.html) + +- [Spring Boot: JPA Mappings – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-2.html) + +- [Spring Boot: JPA Relationships – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-3.html) + +- [Spring Boot: JPA Queries – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-4.html) + +- [Spring Boot: JPA Inherence – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-jpa-5.html) + +- [Spring Boot: Scaling – albertprofe wiki](https://albertprofe.dev/springboot/boot-concepts-scaling.html) + +### Summary + +JPA stands for `Java Persistence API`. + +It is a Java specification for **managing, persisting, and accessing relational data** in Java applications. + +JPA is a **standard API for ORM (Object-Relational Mapping)** and provides a way to map Java objects to relational databases + +## Word 1:n Pronunciation Bidirectional + +```java +@Entity +public class Word { + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; +} + +// .. +@Entity +public class Pronunciation { + @Id + private String id; + private String audioDescription; + private long audioDuration; + private long audioSize; + private String audioUrl; + private String definition; + private String phoneticSpelling; + private String speakerGender; + public enum Type { + RECORDED, SAMPLE + } + private Type type; +} +``` + +**Step 1: Define the Relationship** + +> We must to identify the relationship as One-to-Many: one Word can have multiple Pronunciations, and each Pronunciation belongs to a unique Word. + +**Step 2: Choose the Annotations** + +We need to use `@OneToMany` in the `Word` entity and `@ManyToOne` in the `Pronunciation` entity. + +**Step 3: Use @JoinColumn** + +The `@JoinColumn` annotation is key for specifying the foreign key **column** in the `Pronunciation` table + +**Step 4: Configure the Annotations** + +We can configure the annotations for better performance and consistency: + +In the `Word` class: + +```java +@OneToMany(mappedBy = "word", cascade = CascadeType.ALL, fetch = FetchType.LAZY) +private List pronunciations = new ArrayList<>(); +``` + +In the `Pronunciation` class: + +```java +@ManyToOne(fetch = FetchType.LAZY) +@JoinColumn(name = "WORD_ID_FK", nullable = false) +private Word word; +``` + +- `cascade = CascadeType.ALL` ensures that operations on Word are cascaded to associated Pronunciations. +- `fetch = FetchType.LAZY` improves performance by loading related entities only when accessed. +- `nullable = false` ensures that every Pronunciation must be associated with a Word. + +```java +@Entity +public class Word { + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + @OneToMany(mappedBy = "word") + private List pronunciations; +} + +// .. +@Entity +public class Pronunciation { + @Id + private String id; + private String audioDescription; + private long audioDuration; + private long audioSize; + private String audioUrl; + private String definition; + private String phoneticSpelling; + private String speakerGender; + + public enum Type { + RECORDED, SAMPLE + } + @Enumerated(EnumType.STRING) + private Type type; + + @JsonIgnore + @ManyToOne + @JoinColumn(name = "WORD_ID_FK") + private Word word; +} +``` + +### Test JUnit + +#### With @SpringBootTest + +Test Class Overview: + +- Uses `@SpringBootTest` annotation, which loads the full application context for testing. +- Autowires `WordRepository` and `PronunciationRepository` for database operations. + +Test Method: AssignTestWord + +1. Creates a new `Pronunciation` object and sets its properties. +2. Saves the `Pronunciation` object to the database. +3. Attempts to find a `Word` by ID "8f7d1b9e3a2c5f6e". +4. If the `Word` is found: + - Associates the `Pronunciation` with the found `Word`. + - Saves the updated `Pronunciation` to the database. + +**Key Points** + +- The test doesn't create a new `Word`, it assumes one exists with ID "8f7d1b9e3a2c5f6e". +- There are no assertions to verify the test results. +- The test demonstrates how to associate a `Pronunciation` with an existing `Word`. +- Commented code suggests alternative approaches that were not implemented. + +**Potential Improvements** + +- Add assertions to verify the relationship is correctly established. +- Create a `Word` object if it doesn't exist to ensure test consistency. +- Use `@DataJpaTest` for more focused repository testing. +- Add error handling for cases where the `Word` is not found. + +```java +@SpringBootTest +public class PronunciationTest { + + @Autowired + WordRepository wordRepository; + @Autowired + PronunciationRepository pronunciationRepository; + + @Test + public void AssignTestWord(){ + + // New Pronunciation object + Pronunciation p1 = new Pronunciation(); + p1.setId("1"); + p1.setAudioDescription("test"); + p1.setAudioDuration(1); + p1.setAudioSize(1); + p1.setAudioUrl("test"); + p1.setDefinition("test"); + p1.setPhoneticSpelling("test"); + p1.setSpeakerGender("test"); + p1.setType(Pronunciation.type.SAMPLE); + + pronunciationRepository.save(p1); + + // get word by id from repository + // by JPA repository by id returning optional container Word/Null + Optional optionalWord = wordRepository.findById("8f7d1b9e3a2c5f6e"); + System.out.println(optionalWord); + + if (optionalWord.isPresent()) { + // get word from optional container + p1.setWord(optionalWord.get()); + pronunciationRepository.save(p1); + } + + } +} +``` + +#### With @DataJpaTest + +Our test case is a good start, but there are a few improvements we can make: + +1. Use `@DataJpaTest` instead of `@SpringBootTest` for repository tests to load only JPA-related components. +2. Use `@Transactional` to ensure each test runs in its own transaction. +3. Add assertions to verify the relationship is correctly established. + +Here's an improved version of our test: + +```java +@DataJpaTest +@Transactional +public class PronunciationTest { + + @Autowired + WordRepository wordRepository; + @Autowired + PronunciationRepository pronunciationRepository; + + @Test + public void testAssignPronunciationToWord() { + // Create and save a Word + Word word = new Word(); + word.setId("8f7d1b9e3a2c5f6e"); + word.setWordName("Test"); + wordRepository.save(word); + + // Create and save a Pronunciation + Pronunciation pronunciation = new Pronunciation(); + pronunciation.setId("1"); + pronunciation.setAudioDescription("test"); + // ... set other fields ... + pronunciation.setWord(word); + pronunciationRepository.save(pronunciation); + + // Verify the relationship + Optional retrievedWord = wordRepository.findById("8f7d1b9e3a2c5f6e"); + assertThat(retrievedWord).isPresent(); + assertThat(retrievedWord.get().getPronunciations()).hasSize(1); + assertThat(retrievedWord.get().getPronunciations().get(0).getId()).isEqualTo("1"); + + Optional retrievedPronunciation = pronunciationRepository.findById("1"); + assertThat(retrievedPronunciation).isPresent(); + assertThat(retrievedPronunciation.get().getWord().getId()).isEqualTo("8f7d1b9e3a2c5f6e"); + } +} +``` + +### Postman Usage + +To test this relationship using Postman: + +1. Create a POST endpoint to create a Word. +2. Create another POST endpoint to create a Pronunciation and associate it with a Word. +3. Create a GET endpoint to retrieve a Word with its associated Pronunciations. + +Example Postman requests: + +1. Create Word: + POST /api/words + + ```json + { + "id": "8f7d1b9e3a2c5f6e", + "wordName": "Example", + "definition": "An instance serving to illustrate" + } + ``` + +2. Create Pronunciation: + POST /api/pronunciations + + ```json + { + "id": "1", + "audioDescription": "Example pronunciation", + "word": { + "id": "8f7d1b9e3a2c5f6e" + } + } + ``` + +3. Retrieve Word with Pronunciations: + GET /api/words/8f7d1b9e3a2c5f6e + +#### Our example + +```json +{ + "id": "8f7d1b9e3a2c5f6e", + "wordName": "aberration", + "definition": "a departure from what is normal, usual, or expected", + "phoneticSpelling": "ˌæbəˈreɪʃən", + "sentence": "The sudden drop in temperature was an aberration for this time of year.", + "level": 3, + "pronunciations": [ + { + "id": "1", + "audioDescription": "test", + "audioDuration": 1, + "audioSize": 1, + "audioUrl": "test", + "definition": "test", + "phoneticSpelling": "test", + "speakerGender": "test", + "type": "SAMPLE" + } + ], + "active": true +``` + +## Word 1:n Pronunciation Unidirectional + +> Let's create the two unidirectional versions of the relationship between `Word` and `Pronunciation`. + +**Version 1: Unidirectional One-to-Many (Word owns the relationship)** + +In this version, the `Word` entity knows about the `Pronunciation` entities, but the `Pronunciation` entity doesn't have a direct reference back to the `Word`. + +```java +@Entity +public class Word { + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + @OneToMany + @JoinColumn(name = "WORD_ID_FK") // Foreign key in Pronunciation table + private List pronunciations; + + // Constructors, getters, and setters + +} + +@Entity +public class Pronunciation { + @Id + private String id; + private String audioDescription; + private long audioDuration; + private long audioSize; + private String audioUrl; + private String definition; + private String phoneticSpelling; + private String speakerGender; + + public enum Type { + RECORDED, SAMPLE + } + @Enumerated(EnumType.STRING) + private Type type; + + // Remove the ManyToOne relationship + // @JsonIgnore + // @ManyToOne + // @JoinColumn(name = "WORD_ID_FK") + // private Word word; + + // Constructors, getters, and setters + +} +``` + +**Changes:** + +* In the `Word` entity, the `@OneToMany` annotation now has a `@JoinColumn(name = "WORD_ID_FK")`. This specifies that the `WORD_ID_FK` column in the `Pronunciation` table will be used as the foreign key. +* In the `Pronunciation` entity, the `@ManyToOne` relationship and the associated `word` field are removed. + +**Version 2: Unidirectional Many-to-One (Pronunciation knows about Word)** + +In this version, a `Pronunciation` entity knows to which `Word` it belongs, but the `Word` entity has no direct knowledge of the `Pronunciation` entities. + +```java +@Entity +public class Word { + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + // Remove the OneToMany relationship + //@OneToMany + //@JoinColumn(name = "WORD_ID_FK") // Foreign key in Pronunciation table + //private List pronunciations; + + // Constructors, getters, and setters +} + +@Entity +public class Pronunciation { + @Id + private String id; + private String audioDescription; + private long audioDuration; + private long audioSize; + private String audioUrl; + private String definition; + private String phoneticSpelling; + private String speakerGender; + + public enum Type { + RECORDED, SAMPLE + } + @Enumerated(EnumType.STRING) + private Type type; + + // Add the ManyToOne relationship + @ManyToOne + @JoinColumn(name = "WORD_ID_FK") + private Word word; + + // Constructors, getters, and setters + +} +``` + +**Changes:** + +* In the `Word` entity, the `@OneToMany` relationship and its associated `pronunciations` field are removed. +* In the `Pronunciation` entity, the `@ManyToOne` relationship is added with the `@JoinColumn(name = "WORD_ID_FK")`. This indicates that the `Pronunciation` entity has a foreign key relationship with the `Word` entity. + +**Important Considerations:** + +* In both unidirectional approaches, the database schema will still include a `WORD_ID_FK` column in the `Pronunciation` table. +* Choosing the appropriate unidirectional approach depends on your application's specific needs and how you intend to query and access the data. The first version will be good if you mostly work with `Word` and their `Pronunciation` and the second one if the oposite. + +## Summary Table + +| Relationship Type | Owner | Inverse | Word (Fields & Annotations) | Pronunciation (Fields & Annotations) | +| ----------------------------------- | ------------- | ------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Bidirectional | Pronunciation | Word | `@OneToMany(mappedBy = "word")`
`private List pronunciations;` | `@ManyToOne`
`@JoinColumn(name = "WORD_ID_FK")`
`private Word word;`
`@JsonIgnore` (optional, to prevent infinite recursion) | +| Unidirectional (Word owns) | Word | Pronunciation | `@OneToMany`
`@JoinColumn(name = "WORD_ID_FK")`
`private List pronunciations;` | No field referencing Word. | +| Unidirectional (Pronunciation owns) | Pronunciation | Word | No field referencing Pronunciation. | `@ManyToOne`
`@JoinColumn(name = "WORD_ID_FK")`
`private Word word;` | + +**Explanation of the Table Columns:** + +* **Relationship Type:** The type of relationship between the `Word` and `Pronunciation` entities (bidirectional, unidirectional from `Word`, unidirectional from `Pronunciation`). +* **Owner:** The entity that "owns" the relationship. The owning side is responsible for managing the foreign key column in the database. +* **Inverse:** The entity on the non-owning (or inverse) side of the relationship. +* **Word (Fields & Annotations):** Shows if Word contains field and annotations. +* **Pronunciation (Fields & Annotations):** Shows if Pronunciation contains field and annotations. + +## Word 1:n StageWord + +Let's integrate the `StageWord` entity into the existing `Word` entity with a **one-to-many** relationship: + +```java +@Entity +public class Word { + + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + @OneToMany(mappedBy = "word", +                cascade = CascadeType.ALL, fetch = FetchType.LAZY)) + private List pronunciations; + + @OneToMany(mappedBy = "word", + cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List stageWords; + + // ... getters and setters ... + +} +``` + +```java +@Entity +public class StageWord { + + @Id + private String id; + + @Enumerated(EnumType.STRING) + private Status status; + + private int listenedQty; + private Date lastUpdatedDateTime; + + @JsonIgnore + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "WORD_ID_FK", nullable = false) + private Word word; + + public enum Status { + DONE, PENDING, FAIL + } +} +``` + +Key points: + +* **`Word` Entity:** + + * `@OneToMany(mappedBy = "word", cascade = CascadeType.ALL, fetch = FetchType.LAZY)`: This annotation establishes the one-to-many relationship. + * `mappedBy = "word"`: Specifies that the `word` field in the `StageWord` entity owns the relationship. + * `cascade = CascadeType.ALL`: Ensures that all operations (persist, merge, remove) on a `Word` entity are cascaded to its associated `StageWord` entities. This is useful for managing the lifecycle of the related entities. + * `fetch = FetchType.LAZY`: Loads the `stageWords` only when they are accessed, improving performance. + +* **`StageWord` Entity:** + + * `@ManyToOne(fetch = FetchType.LAZY)`: Establishes the many-to-one relationship back to the `Word` entity. + * `@JoinColumn(name = "WORD_ID_FK", nullable = false)`: Specifies the foreign key column in the `StageWord` table that references the `Word` table. `nullable = false` ensures that every `StageWord` must be associated with a `Word`. + * `@JsonIgnore`: This annotation prevents infinite recursion during serialization when fetching `Word` objects with their associated `StageWord` objects, which in turn would try to serialize the `Word` object again. + +## Enum + +- [Enum](https://drive.google.com/drive/folders/1I68HPNDff_XCo0NbxuJjlOkap0kUNTxe?usp=drive_link) + +- [Enum Oracle](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html) + +> `enum` is commonly used in JPA entities to represent a field that can only have a specific set of predefined values, providing type safety and clear semantics in your Java code while ensuring proper persistence in the database. + +1. Enum Definition: + + ```java + public enum Type { + RECORDED, SAMPLE + } + ``` + + This defines an enumeration called `Type` with two possible values: `RECORDED` and `SAMPLE`. `Enums` in Java are used to define a fixed set of constants. + +2. Field Declaration: + + ```java + @Enumerated(EnumType.STRING) + private Type type; + ``` + + This declares a private field named `type` of the `Type` enum type. + + The `@Enumerated` annotation is used to specify how the enum should be persisted in the database. + +Key points: + +1. The `@Enumerated` annotation is from the JPA (Java Persistence API) and is used to specify how an enum type should be persisted in the database. + +2. `EnumType.STRING` indicates that the enum will be stored as a string in the database. This means the actual names of the enum constants (`"RECORDED"` or `"SAMPLE"`) will be stored, rather than their ordinal values. + +3. Using `EnumType.STRING` is often preferred over the default `EnumType.ORDINAL` because it's more robust against changes in the enum order and provides better readability in the database. + +4. When retrieving data from the database, **JPA will automatically convert the string back into the corresponding enum value.** diff --git a/backend/resources/pronunciationApp-v0.4.md b/backend/resources/pronunciationApp-v0.4.md new file mode 100644 index 000000000..fbfa2751c --- /dev/null +++ b/backend/resources/pronunciationApp-v0.4.md @@ -0,0 +1,168 @@ +# PronunciationApp Backend v0.4 + +**Reference links**: + +- [pronunciationApp model· GitHub](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/resources/jpa/model) +- [pronunciationApp-v0.3.md GitHub](https://github.com/AlbertProfe/pronunciationApp/blob/backend-spring-boot/backend/resources/pronunciationApp-v0.3.md) +- [userBorrowBook sanbox repo](https://github.com/AlbertProfe/userBorrowBook) + +## One-To-One + +```java +@Entity +public class AppUser { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + private String id; + + private String aapUserName; + private int age; + private String email; + private String password; + private int totalScore; + private boolean isActive; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + private GameProgress gameProgress; +} + + +// ... + +@Entity +public class GameProgress { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + private String id; + + private int currentScore; + + public enum Stage { + STAGE_01, STAGE_02, STAGE_03, STAGE_04, STAGE_05 + } + + @Enumerated(EnumType.STRING) + private Stage currentStage; + + @Temporal(TemporalType.TIMESTAMP) + private Date lastPlayedDate; + + private int wordsLearned; + + @OneToOne + @JoinColumn(name = "appUser_id") + private AppUser appUser; +} +``` + +### One-to-One Relationship in JPA: AppUser and GameProgress + +**Bidirectional Relationship** + +In this example, we have a bidirectional one-to-one relationship between `AppUser` and `GameProgress`. Here's a simple explanation: + +1. **AppUser Entity:** + + java + + `@OneToOne(mappedBy = "user", cascade = CascadeType.ALL) private GameProgress gameProgress;` + + - This annotation indicates that each `AppUser` is associated with one `GameProgress`. + + - `mappedBy = "user"` specifies that `GameProgress` owns the relationship. + + - `cascade = CascadeType.ALL` means that operations on `AppUser` will cascade to the associated `GameProgress`. + +2. **GameProgress Entity:** + + java + + `@OneToOne @JoinColumn(name = "user_id") private AppUser appUser;` + + - This annotation shows that each `GameProgress` is linked to one `AppUser`. + + - `@JoinColumn(name = "user_id")` creates a column in the `GameProgress` table to store the `AppUser`'s ID. + +**Bidirectional vs Unidirectional** + +Bidirectional (Current Setup): + +- Both entities have a reference to each other. + +- You can navigate from `AppUser` to `GameProgress` and vice versa. + +- Useful when you need to access related data from both sides frequently. + +Unidirectional (Alternative): + +If we remove the `gameProgress` field from `AppUser`, it becomes unidirectional: + +- Only `GameProgress` would have a reference to `AppUser`. + +- You could navigate from `GameProgress` to `AppUser`, but not the other way around. + +- Simpler, but limits navigation to one direction. + +**Key Points** + +- In a bidirectional relationship, one side must own the relationship (here, `GameProgress`). + +- The non-owning side uses `mappedBy` to indicate the owning side's field name. + +- Bidirectional relationships offer more flexibility but require careful management to maintain consistency. + +## Many-To-Many + +In JPA (Java Persistence API) with Spring Boot, many-to-many relationships between entities can be implemented in **two main ways**: + +1. **Using `@ManyToMany` with `@JoinTable`:** + + - This approach can be configured as either **unidirectional** or **bidirectional**. + + - It relies on a **join table** managed by JPA through the `@JoinTable` annotation. + + - Example: + + ```java + @ManyToMany + @JoinTable( + name = "student_course", + joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "course_id") + ) + private Set courses; + ``` + + - **Use case:** Ideal for simple relationships where the join table does not require additional attributes. + +2. **Using an Intermediate Entity with `@OneToMany` and `@ManyToOne`:** + + - This approach involves creating an **intermediate entity** to represent the relationship explicitly. + + - The intermediate entity acts as an **assignment table** and connects the two main entities using `@OneToMany` and `@ManyToOne`. + + - Example: + + ```java + @Entity + public class StudentCourse { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "student_id") + private Student student; + + @ManyToOne + @JoinColumn(name = "course_id") + private Course course; + + // Additional attributes (e.g., enrollment date, grade) + private LocalDate enrolledDate; + } + ``` + + - **Use case:** Necessary when the relationship requires additional attributes or when more control over the join table is needed. diff --git a/backend/resources/springboot-for-5yo.md b/backend/resources/springboot-for-5yo.md new file mode 100644 index 000000000..37463525d --- /dev/null +++ b/backend/resources/springboot-for-5yo.md @@ -0,0 +1,41 @@ +# Spring Boot and MVC to a 5yo kid + +> Imagine a big restaurant called `Spring Boot`. + +Here's how it works: + +- The menu is like the `View`. It shows you all the yummy food you can order. + +- When you come in, you meet the maitre. They're like the `Front Controller`, welcoming you and guiding you to your table. + +- The waiter is like the `Controller`. They take your order and make sure you get what you wan. + +- Once the waiter has the order, our restaurant is so big that all the waiters are queuing to deliver the orders to the **kitchen**: it's where all the food is prepared. Our restaurant has a orders-board where the waiter boss pins the order, it is like the `Service`. + +- In the kitchen, there's a chef who's like `JPA`. They know all the recipes and how to make the dishes just right. + +- The cook is like `SQL queries.` They do the actual cooking, following the chef's instructions. + +- Each plate has a super-sercret code just for superheroes, this secret code is the `id` at `database`. + +- All the ingredients are kept in the pantry and fridge, which is like the `database`. + +- And finally, the plates and dishes are like the `Model`. They hold all the delicious food that comes to your table. + +- Imagine you want pizza at home. The `API Rest` is like a super-fast delivery superhero who brings your yummy food exactly where you want it! + +- What if you could watch chefs cooking? The `WebSocket` is like a magical window where you see everything happening in the kitchen, right when it's happening! + +- The `Security` is like a kind and strong firend who makes sure only nice people can enter and keeps all the secret recipes safe. + +- The `Thymeleaf Templates` and `Vaadin` create super cool, colorful menus that make you smile and want to try everything. + +- When you want the perfect meal, the `AI` is like a magic waiter who knows exactly what you like and suggests the most delicious food just for you! + +- Someone needs to make sure everything works perfectly. The `Actuator` is like the restaurant's superhero manager who watches over everything. + +- What if restaurants could help each other? The `Spring Cloud` is like a big friendly restaurant family that shares cool tricks and helps everyone. + +- Want to cook something amazing super fast? The `Spring Initializr` is like a magic kitchen that appears instantly, ready to make any delicious meal you want! + +All these parts work together to make sure you have a great meal at the Spring Boot restaurant! diff --git a/frontend/PRACTICA_01_FRONT/Creacion de la app para los usuarios_React.md b/frontend/PRACTICA_01_FRONT/Creacion de la app para los usuarios_React.md new file mode 100644 index 000000000..0af44d910 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/Creacion de la app para los usuarios_React.md @@ -0,0 +1,99 @@ +### Creado el proyecto con vite + ++ instalare axios + ++ instalare MaterialUi + +### Creacion de la app para los usuarios + ++ creare el componente Users.jsx + ++ importare MaterialUi + ++ Importare el archivo data-api.js + ++ creare el export default de users + ++ creare un useState para el componente Users + ++ creare el userEffect con una funcion arrou llamando al metodo getUsers + ++ hare unanllamada a la data-api.js con la funcin fetchUsers + ++ hi seteare el data a users con la funcion setUsers + ++ todo hira anidado co n un try catch + ++ al final de la funcion Users hare el getUsers [] con el array vacio para que se renderize todo + ++ en return hacemos el render con componentes como card y typhogrphy + ++ hare un map de words para que me renderixe tantas veces como palabras tenga + ++ luego llamare a la variable word.word para renderizar el component + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-17-29-31-image.png) + +### creacion del data api para axios + ++ Primero importare axios + ++ creare la constante BASE_URL i le pasare la url que he creado en el servidor moc de postman + ++ creare el metodo fetchusers con una funcion arrow + ++ dentro de un try catch hare una llamada a axios con el axios.get y añadire la constante donde tengo almacenada la url de postman + ++ este procedimiento lo he seguido tanto para words como para users + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-17-39-34-image.png) + +### Configuracion del Utils.js + ++ aqui solo hay una funcion con una llamada a la pagina de imgur.com donde se le pide que devuelva una imagen hi el formato + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-17-47-17-image.png) + +### Root donde se hace la llamada a la App + ++ En la pagina principal de la app creare el archivo router + ++ importare las librerias BrowserRouter, Route, Routes de react router.dom + ++ importare el resto de componentes de la app + ++ en la funcion App en el return utilizare BrowserRouter, Routes, Route, parapasarle + ++ los componentes a renderizar + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-05-10-10-58-image.png) + +### Creando el header con el componente layout + ++ importare iLink, Outlet de react-router-dom, AppBar, Toolbar, Typography, Button, Container y styled de MaterialUi + ++ crearemos el metodo Styled donde se crearan estilos para la barra de navegacion + ++ creare la funcion Layout hi solo le parare el renderizado con estilos y componentes como button y typogrphy + ++ en la botonera le pasare el componente de Material StyledLinked para darle formato hi el path de los componentes + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-17-54-55-image.png) + +### Creacion del severMoc en postman + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-18-04-22-image.png) + +### Respuesta del server moc con un 200 ok + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-18-06-11-image.png) + +### Render del proyecto + ++ eta es la parte de words + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-18-06-57-image.png) + ++ Parte de users + +![](/home/pcpla/snap/marktext/9/.config/marktext/images/2025-02-07-18-08-13-image.png) diff --git a/frontend/PRACTICA_01_FRONT/User.json b/frontend/PRACTICA_01_FRONT/User.json new file mode 100644 index 000000000..93f5643f1 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/User.json @@ -0,0 +1,123 @@ +[ + { + "id": "001", + "name": "Akira Tanaka", + "age": "28", + "is_Active": true + }, + { + "id": "002", + "name": "Yuki Sato", + "age": "35", + "is_Active": false + }, + { + "id": "003", + "name": "Haruto Suzuki", + "age": "42", + "is_Active": true + }, + { + "id": "004", + "name": "Rin Watanabe", + "age": "19", + "is_Active": true + }, + { + "id": "005", + "name": "Kenji Yamamoto", + "age": "56", + "is_Active": true + }, + { + "id": "006", + "name": "Aoi Nakamura", + "age": "31", + "is_Active": false + }, + { + "id": "007", + "name": "Sakura Ito", + "age": "24", + "is_Active": true + }, + { + "id": "008", + "name": "Ryu Kobayashi", + "age": "39", + "is_Active": true + }, + { + "id": "009", + "name": "Hana Kato", + "age": "27", + "is_Active": true + }, + { + "id": "010", + "name": "Takeshi Saito", + "age": "45", + "is_Active": false + }, + { + "id": "011", + "name": "Mei Abe", + "age": "22", + "is_Active": true + }, + { + "id": "012", + "name": "Hiroshi Yamada", + "age": "50", + "is_Active": true + }, + { + "id": "013", + "name": "Yui Matsumoto", + "age": "33", + "is_Active": false + }, + { + "id": "014", + "name": "Kazuki Inoue", + "age": "29", + "is_Active": true + }, + { + "id": "015", + "name": "Emi Takahashi", + "age": "37", + "is_Active": true + }, + { + "id": "016", + "name": "Sora Kimura", + "age": "26", + "is_Active": true + }, + { + "id": "017", + "name": "Naoki Hashimoto", + "age": "41", + "is_Active": false + }, + { + "id": "018", + "name": "Mio Sasaki", + "age": "30", + "is_Active": true + }, + { + "id": "019", + "name": "Daiki Fujita", + "age": "34", + "is_Active": true + }, + { + "id": "020", + "name": "Ayumi Ishikawa", + "age": "25", + "is_Active": false + } +] + diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/.gitignore b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/.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/PRACTICA_01_FRONT/pronunciationAppFront/README.md b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/README.md new file mode 100644 index 000000000..f768e33fc --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/README.md @@ -0,0 +1,8 @@ +# React + 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 diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/eslint.config.js b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/eslint.config.js new file mode 100644 index 000000000..238d2e4e6 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/eslint.config.js @@ -0,0 +1,38 @@ +import js from '@eslint/js' +import globals from 'globals' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + settings: { react: { version: '18.3' } }, + plugins: { + react, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + ...reactHooks.configs.recommended.rules, + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/index.html b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/index.html new file mode 100644 index 000000000..0c589eccd --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/package-lock.json b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/package-lock.json new file mode 100644 index 000000000..0e5ca567d --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/package-lock.json @@ -0,0 +1,4940 @@ +{ + "name": "pronunciationappfront", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pronunciationappfront", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.1.1", + "@mui/icons-material": "^6.4.0", + "@mui/material": "^6.4.0", + "axios": "^1.7.9", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.17.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "react-router-dom": "^7.1.3", + "vite": "^6.0.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", + "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", + "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.5", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", + "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/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-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fontsource/roboto": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz", + "integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow==", + "license": "Apache-2.0" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.0.tgz", + "integrity": "sha512-6u74wi+9zeNlukrCtYYET8Ed/n9AS27DiaXCZKAD3TRGFaqiyYSsQgN2disW83pI/cM1Q2lJY1JX4YfwvNtlNw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.0.tgz", + "integrity": "sha512-zF0Vqt8a+Zp2Oz8P+WvJflba6lLe3PhxIz1NNqn+n4A+wKLPbkeqY8ShmKjPyiCTg0RMbPrp993oUDl9xGsDlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.0.tgz", + "integrity": "sha512-hNIgwdM9U3DNmowZ8mU59oFmWoDKjc92FqQnQva3Pxh6xRKWtD2Ej7POUHMX8Dwr1OpcSUlT2+tEMeLb7WYsIg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.0", + "@mui/system": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.4.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.0.tgz", + "integrity": "sha512-rNHci8MP6NOdEWAfZ/RBMO5Rhtp1T6fUDMSmingg9F1T6wiUeodIQ+NuTHh2/pMoUSeP9GdHdgMhMmfsXxOMuw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.0.tgz", + "integrity": "sha512-wTDyfRlaZCo2sW2IuOsrjeE5dl0Usrs6J7DxE3GwNCVFqS5wMplM2YeNiV3DO7s53RfCqbho+gJY6xaB9KThUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.0", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-woOTATWNsTNR3YBh2Ixkj3l5RaxSiGoC9G8gOpYoFw1mZM77LWJeuMHFax7iIW4ahK0Cr35TF9DKtrafJmOmNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", + "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz", + "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz", + "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz", + "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz", + "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz", + "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz", + "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz", + "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz", + "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz", + "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz", + "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz", + "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz", + "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz", + "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz", + "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz", + "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz", + "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz", + "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz", + "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz", + "integrity": "sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.10.4", + "@swc/core-darwin-x64": "1.10.4", + "@swc/core-linux-arm-gnueabihf": "1.10.4", + "@swc/core-linux-arm64-gnu": "1.10.4", + "@swc/core-linux-arm64-musl": "1.10.4", + "@swc/core-linux-x64-gnu": "1.10.4", + "@swc/core-linux-x64-musl": "1.10.4", + "@swc/core-win32-arm64-msvc": "1.10.4", + "@swc/core-win32-ia32-msvc": "1.10.4", + "@swc/core-win32-x64-msvc": "1.10.4" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.4.tgz", + "integrity": "sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.4.tgz", + "integrity": "sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.4.tgz", + "integrity": "sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.4.tgz", + "integrity": "sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.4.tgz", + "integrity": "sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.4.tgz", + "integrity": "sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.4.tgz", + "integrity": "sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.4.tgz", + "integrity": "sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.4.tgz", + "integrity": "sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.4.tgz", + "integrity": "sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "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/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + }, + "node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.2.tgz", + "integrity": "sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew==", + "dev": true, + "dependencies": { + "@swc/core": "^1.7.26" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "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/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-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/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/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/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-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==", + "engines": { + "node": ">=0.4.0" + } + }, + "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/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-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.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", + "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 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", + "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", + "dev": true, + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "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/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/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-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/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-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/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "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==", + "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/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", + "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-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.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-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.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "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.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "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/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/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "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.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/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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "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/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.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "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.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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-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==" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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.1.1", + "source-map-js": "^1.2.1" + }, + "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/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/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "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/react-router": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.3.tgz", + "integrity": "sha512-EezYymLY6Guk/zLQ2vRA8WvdUhWFEj5fcE3RfWihhxXBW7+cd1LsIiA3lmx+KCmneAGQuyBv820o44L2+TtkSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.3.tgz", + "integrity": "sha512-qQGTE+77hleBzv9SIUIkGRvuFBQGagW+TQKy53UTZAO/3+YFNBYvRsNIZ1GT17yHbc63FylMOdS+m3oUriF1GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-router": "7.1.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "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==", + "license": "BSD-3-Clause", + "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/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", + "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.30.1", + "@rollup/rollup-android-arm64": "4.30.1", + "@rollup/rollup-darwin-arm64": "4.30.1", + "@rollup/rollup-darwin-x64": "4.30.1", + "@rollup/rollup-freebsd-arm64": "4.30.1", + "@rollup/rollup-freebsd-x64": "4.30.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", + "@rollup/rollup-linux-arm-musleabihf": "4.30.1", + "@rollup/rollup-linux-arm64-gnu": "4.30.1", + "@rollup/rollup-linux-arm64-musl": "4.30.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", + "@rollup/rollup-linux-riscv64-gnu": "4.30.1", + "@rollup/rollup-linux-s390x-gnu": "4.30.1", + "@rollup/rollup-linux-x64-gnu": "4.30.1", + "@rollup/rollup-linux-x64-musl": "4.30.1", + "@rollup/rollup-win32-arm64-msvc": "4.30.1", + "@rollup/rollup-win32-ia32-msvc": "4.30.1", + "@rollup/rollup-win32-x64-msvc": "4.30.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^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.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-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/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "dev": true, + "license": "ISC" + }, + "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/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/vite": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", + "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/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.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "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/PRACTICA_01_FRONT/pronunciationAppFront/package.json b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/package.json new file mode 100644 index 000000000..6471c9926 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/package.json @@ -0,0 +1,35 @@ +{ + "name": "pronunciationappfront", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.1.1", + "@mui/icons-material": "^6.4.0", + "@mui/material": "^6.4.0", + "axios": "^1.7.9", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.17.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "react-router-dom": "^7.1.3", + "vite": "^6.0.5" + } +} diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.css b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.css new file mode 100644 index 000000000..492bf1e25 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.css @@ -0,0 +1,51 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; + +} + +.read-the-docs { + color: #888; +} + +ul { + list-style-type: none; +} +.cabecera{ + width:400px; +} \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.jsx new file mode 100644 index 000000000..5f680e820 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/App.jsx @@ -0,0 +1,26 @@ + +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import About from '/src/Users.jsx'; +import './App.css'; +import Home from '/src/Home.jsx'; +import Layout from '/src/Layout.jsx'; +import Users from '/src/Users.jsx'; +import Words from '/src/Words.jsx'; + + +export default function App() { + + + return ( + + + }> + } /> + } /> + } /> + + + + + ); +}; \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Cards.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Cards.jsx new file mode 100644 index 000000000..ddaf7e118 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Cards.jsx @@ -0,0 +1,82 @@ +import { useState, useEffect } from "react"; +import { + Box, + Card, + CardContent, + Typography, + Container, + Grid, + Avatar +} from "@mui/material"; +import { fetchWords } from "./data-api"; +import { Button } from "@material-ui/core"; + +export default function WordList() { + const [words, setWords] = useState([]); + + useEffect(() => { + const getWords = async () => { + try { + const data = await fetchWords(); + setWords(data); + } catch (error) { + console.error("Failed to fetch words:", error); + } + }; + + getWords(); + }, []); + + return ( + + + + + Word List + + + {words.map((word) => ( + + + + jou + + + + {word.word} + + + + Pronunciation: {word.pronunciation},

+ Difficulty: {word.difficulty} +
+
+
+
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Header.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Header.jsx new file mode 100644 index 000000000..72940ccad --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Header.jsx @@ -0,0 +1,34 @@ + +export default function Header(){ + + + return ( + <> +

Learn English once and for all

+
+

Practice Real-World Conversations

+ Practice Real-World Conversations +
    +
  • + Speak: + practice your pronunciation +
  • +
  • + Drill: + repeat until perfection! +
  • +
  • + Listen: + get used to real english sounds +
  • +
+
+ + ); + + + } \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Home.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Home.jsx new file mode 100644 index 000000000..076bff049 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Home.jsx @@ -0,0 +1,16 @@ +import Header from './Header.jsx' + + +export default function Home() { + + return ( + <> +

Home

+ +

Welcome to PronunciationApp

+
+ + + ); + +} \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Layout.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Layout.jsx new file mode 100644 index 000000000..0ef00c65a --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Layout.jsx @@ -0,0 +1,50 @@ +import { Link, Outlet } from "react-router-dom"; +import { AppBar, Toolbar, Typography, Button, Container } from "@mui/material"; +import { styled } from "@mui/system"; + + +const StyledLink = styled(Link)({ + color: "#4b6cb7", // Matching the soft light gray text color + textDecoration: "none", + marginLeft: "20px", + "&:hover": { + color: "#0B0B0B", // Slightly lighter on hover + }, +}); + +export default function Layout() { + return ( + <> + + + + pronunciationApp + + + + + + + + {" "} + {/* Ensuring consistent text color */} + + + + ); +} \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/NoPage.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/NoPage.jsx new file mode 100644 index 000000000..51f7f330c --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/NoPage.jsx @@ -0,0 +1,3 @@ +export default function NoPage () { + return

404

; + }; \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Users.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Users.jsx new file mode 100644 index 000000000..e84ea90aa --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Users.jsx @@ -0,0 +1,91 @@ +import { useState, useEffect } from "react"; +import './App.css' +import { + Box, + Card, + CardContent, + Typography, + Container, + Button, + Grid, + Avatar +} from "@mui/material"; +import { fetchUsers } from "./data-api"; + +export default function Users() { + const [users, setUsers] = useState([]); + + useEffect(() => { + const getUsers= async () => { + try { + const data = await fetchUsers(); + setUsers(data); + } catch (error) { + console.error("Failed to fetch words:", error); + } + }; + + getUsers(); + }, []); + + + return ( + + + + + + User List + + + {users.map((users) => ( + + + + + + + + + {users.users} + {users.Avatar} + + + + name: {users.name},

+ Age: {users.age},

+ email: {users.email}

+
+ +
+
+
+ ))} +
+
+
+ + ); + +} \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Words.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Words.jsx new file mode 100644 index 000000000..04758932c --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/Words.jsx @@ -0,0 +1,16 @@ +import Cards from './Cards' + + +export default function Words(){ + + + return ( + + <> + {" "} +

Practice

+ + + + +)}; \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/Avatar.png b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/Avatar.png new file mode 100644 index 000000000..c18dc911c Binary files /dev/null and b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/Avatar.png differ diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/children-going-to-school-cartoon-vector.jpg b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/children-going-to-school-cartoon-vector.jpg new file mode 100644 index 000000000..d94a33e0a Binary files /dev/null and b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/children-going-to-school-cartoon-vector.jpg differ diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/react.svg b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/data-api.js b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/data-api.js new file mode 100644 index 000000000..26485be75 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/data-api.js @@ -0,0 +1,24 @@ +// api.js +import axios from "axios"; + +const BASE_URL = "https://7e7270e1-910c-409e-a408-b5cbd7aa1da8.mock.pstmn.io" +export const fetchWords = async () => { + try { + const response = await axios.get(`${BASE_URL}/ProbaWord`); + return response.data.words; + + } catch (error) { + console.error("Error fetching words:", error); + throw error; + } +}; +const BASE_URL1 ="https://6443a4d5-fd22-408a-b62e-dd3157502954.mock.pstmn.io" +export const fetchUsers = async () => { + try { + const response = await axios.get(`${BASE_URL1}/TestUser`); + return response.data.users; + } catch (error) { + console.error("Error fetching Users:", error); + throw error; + } +}; \ No newline at end of file diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/index.css b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/index.css new file mode 100644 index 000000000..821adcb7f --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/index.css @@ -0,0 +1,73 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background: linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%); + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} +img{ + width:4em; + margin-top:10px; + +} diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/main.jsx b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/main.jsx new file mode 100644 index 000000000..9f03db3e3 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/main.jsx @@ -0,0 +1,11 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/utils.js b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/utils.js new file mode 100644 index 000000000..1bbf70193 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/src/utils.js @@ -0,0 +1,4 @@ +export function getImageUrl(imageId, size = "s") { +src="https://i.imgur.com/" + return "" + imageId + size + ".jpg"; +} diff --git a/frontend/PRACTICA_01_FRONT/pronunciationAppFront/vite.config.js b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/vite.config.js new file mode 100644 index 000000000..2328e170b --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/pronunciationAppFront/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/frontend/PRACTICA_01_FRONT/words .json b/frontend/PRACTICA_01_FRONT/words .json new file mode 100644 index 000000000..be4f3e807 --- /dev/null +++ b/frontend/PRACTICA_01_FRONT/words .json @@ -0,0 +1,75 @@ + +{ + "words": [ + { + "id": "1", + "word": "benevolent", + "pronunciation": "/bɪˈnɛvələnt/", + "difficulty": "medium" + + }, + { + "id": "2", + "word": "serendipity", + "pronunciation": "/ˌserənˈdɪpɪti/", + "difficulty": "high" + + }, + { + "id": "3", + "word": "ephemeral", + "pronunciation": "/ɪˈfɛmərəl/", + "difficulty": "medium" + + }, + { + "id": "4", + "word": "ubiquitous", + "pronunciation": "/juːˈbɪkwɪtəs/", + "difficulty": "high" + + }, + { + "id": "5", + "word": "mellifluous", + "pronunciation": "/məˈlɪfluəs/", + "difficulty": "high" + + }, + { + "id": "6", + "word": "eloquent", + "pronunciation": "/ˈɛləkwənt/", + "difficulty": "medium" + + }, + { + "id": "7", + "word": "quintessential", + "pronunciation": "/ˌkwɪntɪˈsenʃəl/", + "difficulty": "high" + + }, + { + "id": "8", + "word": "ethereal", + "pronunciation": "/ɪˈθɪəriəl/", + "difficulty": "high" + + }, + { + "id": "9", + "word": "surreptitious", + "pronunciation": "/ˌsʌrəpˈtɪʃəs/", + "difficulty": "high" + + }, + { + "id": "10", + "word": "labyrinthine", + "pronunciation": "/ˌlæbəˈrɪnθaɪn/", + "difficulty": "high" + + } + ] +}