commit 201d604ca2d337e75360b09d3e698d9863537ab7 Author: Anton Dzyk Date: Sat Oct 12 20:21:38 2024 +0300 Первая версия базовых компонент diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2546fd3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea + +**/target + +**/node_modules \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..48d62b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] Dzyk Anton + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4fdcc9d --- /dev/null +++ b/Readme.md @@ -0,0 +1,52 @@ +Базовые компоненты обучающего портала DevOps-практик +==================================================== + +Данный проект содержит базовые компоненты для построения обучающего портала DevOps-практикам. + +Стек +---- + +1. API Server + * Java 21, Spring Boot 3 +2. Engine (Docker) + * Golang 1.21 +3. Frontend + * Vue.js, axios + + +Связанные сервисы +----------------- + +1. Keycloak +2. PostgreSQL +3. Kafka + + +Сборка +------ + +```shell +cd src/backend/api-server +docker build -t api-server . +``` + +```shell +cd src/backend/engines/docker +docker build -t docker-engine . +``` + +```shell +cd src/frontend/user-frontend +docker build -t user-frontend . +``` + +Локальная разработка +-------------------- + +Для разработки необходимо [запустить](./debug/Readme.md) связанные сервисы. + + +Лицензия +-------- + +Данный проект распространяется под лицензией [Apache 2.0](./LICENSE) \ No newline at end of file diff --git a/debug/.env b/debug/.env new file mode 100644 index 0000000..2433c54 --- /dev/null +++ b/debug/.env @@ -0,0 +1,3 @@ +KEYCLOAK_VERSION=24.0.5 +KAFKA_IMAGE_VERSION=7.7.1 +POSTGRESQL_VERSION=16 \ No newline at end of file diff --git a/debug/Readme.md b/debug/Readme.md new file mode 100644 index 0000000..72d8775 --- /dev/null +++ b/debug/Readme.md @@ -0,0 +1,14 @@ +Запуск окружения для разработки +=============================== + + +```shell +docker compose up -d +``` + +Пересоздание стенда +```shell +docker compose down +docker volume rm education_system +docker compose up -d +``` \ No newline at end of file diff --git a/debug/configs/application.properties b/debug/configs/application.properties new file mode 100644 index 0000000..55181bd --- /dev/null +++ b/debug/configs/application.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:postgresql://localhost:5432/education_system +spring.datasource.username=postgres +spring.datasource.password=postgres \ No newline at end of file diff --git a/debug/configs/init-db.sh b/debug/configs/init-db.sh new file mode 100755 index 0000000..232d576 --- /dev/null +++ b/debug/configs/init-db.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +# Keycloak +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER keycloak PASSWORD 'keycloak'; + CREATE DATABASE keycloak OWNER keycloak; +EOSQL + +# API Server +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER education_system PASSWORD 'education_system'; + CREATE DATABASE education_system OWNER education_system; +EOSQL diff --git a/debug/docker-compose.yml b/debug/docker-compose.yml new file mode 100644 index 0000000..dbc2469 --- /dev/null +++ b/debug/docker-compose.yml @@ -0,0 +1,102 @@ +name: education-system + +services: + postgres: + container_name: edu_postgres + image: postgres:${POSTGRESQL_VERSION} + restart: always + volumes: + - education_system_postgres:/var/lib/postgresql/data + - ./configs/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh + ports: + - "5432:5432" + networks: + - education_system + environment: + POSTGRES_PASSWORD: postgres + + keycloak: + container_name: edu_keycloak + restart: on-failure:5 + image: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} + environment: + KC_LOG_LEVEL: debug + KC_DB: postgres + KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak' + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: keycloak + KC_DB_SCHEMA: public + KC_HOSTNAME: portal.local + KC_HOSTNAME_PATH: keycloak + KC_HTTP_RELATIVE_PATH: keycloak + KC_HTTP_ENABLED: true + HTTP_ADDRESS_FORWARDING: true + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + ports: + - '8080:8080' + command: start-dev + networks: + - education_system + depends_on: + - postgres + + zookeeper: + image: confluentinc/cp-zookeeper:${KAFKA_IMAGE_VERSION} + container_name: edu_zookeeper + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - "2181:2181" + + kafka: + image: confluentinc/cp-kafka:${KAFKA_IMAGE_VERSION} + container_name: edu_kafka + depends_on: + - zookeeper + ports: + - "29092:29092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + + init-kafka: + container_name: edu_init_kafka + image: confluentinc/cp-kafka:${KAFKA_IMAGE_VERSION} + depends_on: + - kafka + entrypoint: [ '/bin/sh', '-c' ] + command: | + " + kafka-topics --bootstrap-server kafka:9092 --create --if-not-exists --topic edu_task_docker --replication-factor 1 --partitions 1 + kafka-topics --bootstrap-server kafka:9092 --create --if-not-exists --topic edu_results --replication-factor 1 --partitions 1 + + kafka-topics --bootstrap-server kafka:9092 --list + " + + kafka-ui: + container_name: edu_kafka_ui + image: provectuslabs/kafka-ui:v0.7.2 + ports: + - 8001:8080 + depends_on: + - kafka + - zookeeper + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 + KAFKA_CLUSTERS_0_METRICS_PORT: 9997 + +volumes: + education_system_postgres: + name: education_system + driver: local + +networks: + education_system: + name: education-system \ No newline at end of file diff --git a/src/backend/api-server/.gitignore b/src/backend/api-server/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/src/backend/api-server/.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/src/backend/api-server/.mvn/wrapper/maven-wrapper.properties b/src/backend/api-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/src/backend/api-server/.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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/src/backend/api-server/mvnw b/src/backend/api-server/mvnw new file mode 100755 index 0000000..d7c358e --- /dev/null +++ b/src/backend/api-server/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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# 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/src/backend/api-server/mvnw.cmd b/src/backend/api-server/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/src/backend/api-server/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 https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@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 - user 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/src/backend/api-server/pom.xml b/src/backend/api-server/pom.xml new file mode 100644 index 0000000..795f9f2 --- /dev/null +++ b/src/backend/api-server/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + + ru.oa2.edu + edu-api-server + 0.0.1-SNAPSHOT + ApiServer + Education System + + + 21 + ${java.version} + ${java.version} + + 3.3.3 + 6.1.12 + 6.3.3 + 24.0.5 + 3.2.3 + + 42.7.2 + 2.1.1 + 3.1.0 + 1.18.34 + 5.10.3 + + 3.13.0 + + + + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-security + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + ${spring-boot.version} + + + + org.keycloak + keycloak-spring-security-adapter + ${keycloak.version} + + + + org.postgresql + postgresql + ${postgresql.version} + + + + jakarta.annotation + jakarta.annotation-api + ${jakarta.annotation-api.version} + + + + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-undertow + ${spring-boot.version} + + + + org.springframework.kafka + spring-kafka + ${spring-kafka.version} + + + + + com.fasterxml.jackson.core + jackson-databind + 2.17.2 + + + + org.projectlombok + lombok + ${lombok.version} + true + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + test + + + + org.springframework + spring-web + ${springframework.version} + + + + org.springframework.security + spring-security-test + ${spring-security-test.version} + test + + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + true + + + + + + diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/ApiServerApplication.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/ApiServerApplication.java new file mode 100644 index 0000000..f3da98d --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/ApiServerApplication.java @@ -0,0 +1,24 @@ +package ru.oa2.edu.api.application; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@ComponentScan(basePackages={"ru.oa2.edu.api.*"}) +@EnableJpaRepositories(basePackages={ + "ru.oa2.edu.api.application.database", + "ru.oa2.edu.api.domain.job" +}) +@EnableTransactionManagement +@EntityScan(basePackages="ru.oa2.edu.api.application.entities") +public class ApiServerApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiServerApplication.class, args); + } + +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/ApplicationConfig.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/ApplicationConfig.java new file mode 100644 index 0000000..dd5b30a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/ApplicationConfig.java @@ -0,0 +1,15 @@ +package ru.oa2.edu.api.application.config; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authorization.AuthorizationEventPublisher; +import org.springframework.security.authorization.SpringAuthorizationEventPublisher; + +public class ApplicationConfig { + + @Bean + public AuthorizationEventPublisher authorizationEventPublisher + (ApplicationEventPublisher applicationEventPublisher) { + return new SpringAuthorizationEventPublisher(applicationEventPublisher); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/CustomSuccessHandler.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/CustomSuccessHandler.java new file mode 100644 index 0000000..fd21695 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/CustomSuccessHandler.java @@ -0,0 +1,34 @@ +package ru.oa2.edu.api.application.config; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; + + +import java.io.IOException; + +@Slf4j +public class CustomSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess(final HttpServletRequest request, + final HttpServletResponse response, final Authentication authentication) + throws IOException, ServletException { + super.onAuthenticationSuccess(request, response, authentication); + + HttpSession session = request.getSession(true); + + try { + var jwtAuth = ((JwtAuthenticationToken) authentication).getTokenAttributes(); + var sub = (String) jwtAuth.get("sub"); + log.info(String.format("SUB: %s", sub)); + } catch (Exception e) { + log.error("Error in getting User info", e); + } + } + +} \ No newline at end of file diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/KafkaConfig.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/KafkaConfig.java new file mode 100644 index 0000000..eed8053 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/KafkaConfig.java @@ -0,0 +1,70 @@ +package ru.oa2.edu.api.application.config; + + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.*; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class KafkaConfig { + + @Value("${kafka.address}") + private String kafkaAddress; + + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap<>(); + configProps.put( + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, + kafkaAddress); + configProps.put( + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer.class); + configProps.put( + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + StringSerializer.class); + return new DefaultKafkaProducerFactory<>(configProps); + } + + @Bean + public ConsumerFactory consumerFactory() { + Map props = new HashMap<>(); + props.put( + ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, + kafkaAddress); + props.put( + ConsumerConfig.GROUP_ID_CONFIG, + "edu_results"); + props.put( + ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class); + props.put( + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class); + return new DefaultKafkaConsumerFactory<>(props); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory + kafkaListenerContainerFactory() { + + ConcurrentKafkaListenerContainerFactory factory = + new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + return factory; + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SaveUser.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SaveUser.java new file mode 100644 index 0000000..79da837 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SaveUser.java @@ -0,0 +1,35 @@ +package ru.oa2.edu.api.application.config; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import ru.oa2.edu.api.application.database.UserJpaRepository; +import ru.oa2.edu.api.domain.user.User; + +import java.io.IOException; +import java.util.function.Supplier; + +@Slf4j +@Component +public class SaveUser implements AuthorizationEventPublisher { + + @Autowired + UserJpaRepository userRepository; + + @Override + public void publishAuthorizationEvent(Supplier authentication, T object, AuthorizationDecision decision) { + if (userRepository.findByInternalId(authentication.get().getName()) == null) { + userRepository.save(new User(authentication.get().getName())); + log.info("User with name {} was added", authentication.get().getName()); + } + } +} \ No newline at end of file diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SecurityConfig.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SecurityConfig.java new file mode 100644 index 0000000..e2858c6 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/config/SecurityConfig.java @@ -0,0 +1,34 @@ +package ru.oa2.edu.api.application.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.representations.AccessToken; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtException; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/actuator/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); + return http.build(); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ApiController.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ApiController.java new file mode 100644 index 0000000..9a218dd --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ApiController.java @@ -0,0 +1,45 @@ +package ru.oa2.edu.api.application.controller; + +import java.util.Collection; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Role; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import ru.oa2.edu.api.domain.task.TaskDTO; +import ru.oa2.edu.api.domain.theme.ThemeDTO; +import ru.oa2.edu.api.application.services.RepositoryService; + +@RestController +@RequestMapping("/api") +public class ApiController { + + private final RepositoryService repositoryService; + + public ApiController(RepositoryService repositoryService) { + this.repositoryService = repositoryService; + } + + @GetMapping() + @RequestMapping("/themes") + @PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')") + public Collection getThemes() { + return repositoryService.listThemes(); + } + + + + @GetMapping() + @RequestMapping("/task/{id}") + @PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')") + public TaskDTO getTask(@PathVariable("id") long id) { + return repositoryService.getTask(id); + } + + @GetMapping("/version") + @PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')") + public String getVersion() { + return "1.0.0"; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/RunController.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/RunController.java new file mode 100644 index 0000000..6dbe378 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/RunController.java @@ -0,0 +1,40 @@ +package ru.oa2.edu.api.application.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.oa2.edu.api.domain.dto.EngineTypes; +import ru.oa2.edu.api.domain.dto.Job; +import ru.oa2.edu.api.application.services.RunnerService; + +@RestController +@RequestMapping("/api") +public class RunController { + + @Autowired + RunnerService runnerService; + + @PutMapping() + @RequestMapping("/run") + public ResponseEntity run(@RequestBody Job job) { + EngineTypes.DOCKER.toString(); + switch (EngineTypes.fromString(job.getEngineType())) { + case EngineTypes.DOCKER, EngineTypes.DOCKER_BUILDX: { + var runId = runnerService.Job("edu_task_docker", job); + return ResponseEntity.ok().body(runId); + } + default: { + var res = ResponseEntity.badRequest(); + res.body("not support engine"); + return res.build(); + } + } + } + + @GetMapping() + @RequestMapping("/run/status/{job_id}") + public ResponseEntity getResult(@PathVariable("job_id") long jobId) { + var result = runnerService.Status(jobId); + return ResponseEntity.ok().body(result); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ThemeController.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ThemeController.java new file mode 100644 index 0000000..94c154a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/ThemeController.java @@ -0,0 +1,37 @@ +package ru.oa2.edu.api.application.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import ru.oa2.edu.api.application.services.RepositoryService; +import ru.oa2.edu.api.domain.theme.ThemeDTO; + +@RestController +@RequestMapping("/api/theme") +public class ThemeController { + + private final RepositoryService repositoryService; + + public ThemeController(RepositoryService repositoryService) { + this.repositoryService = repositoryService; + } + + @GetMapping("{id}") + @PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')") + public ThemeDTO getTheme(@PathVariable("id") Long id) { + return repositoryService.getTheme(id); + } + + @PostMapping + @PreAuthorize("hasAnyRole('CURATOR', 'ADMIN')") + public ResponseEntity createTheme(@RequestBody ThemeDTO themeDTO) { + repositoryService.createTheme(themeDTO); + return ResponseEntity.ok().build(); + } + + @PutMapping("{id}") + @PreAuthorize("hasAnyRole('CURATOR', 'ADMIN')") + public ThemeDTO updateTheme(@PathVariable("id") Long id, @RequestBody ThemeDTO themeDTO) { + return repositoryService.updateTheme(id, themeDTO); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/UserController.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/UserController.java new file mode 100644 index 0000000..5eaff5a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/controller/UserController.java @@ -0,0 +1,37 @@ +package ru.oa2.edu.api.application.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.oa2.edu.api.application.services.RepositoryService; +import ru.oa2.edu.api.domain.user.UserInfo; +import ru.oa2.edu.api.domain.user.UserRepository; +import ru.oa2.edu.api.domain.user.UserStatistic; + +@RestController +@RequestMapping("/api/user") +public class UserController { + + @Autowired + RepositoryService repositoryService; + + @Autowired + UserRepository userRepository; + + @GetMapping("/current") + public UserInfo getCurrentUser(Authentication authentication) { + var jwtAuth = ((JwtAuthenticationToken) authentication).getTokenAttributes(); + + var user = userRepository.findByInternalId(authentication.getName()); + return new UserInfo( + authentication.getName(), + jwtAuth.get("family_name").toString(), + jwtAuth.get("given_name").toString(), + jwtAuth.get("email").toString(), + repositoryService.getUserStatistic(user.getId()) + ); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/JobJpaRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/JobJpaRepository.java new file mode 100644 index 0000000..e31e504 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/JobJpaRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.application.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.oa2.edu.api.domain.job.Job; +import ru.oa2.edu.api.domain.job.JobRepository; + +@Repository +public interface JobJpaRepository extends JpaRepository, JobRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/TaskJpaRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/TaskJpaRepository.java new file mode 100644 index 0000000..5da255e --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/TaskJpaRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.application.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.oa2.edu.api.domain.task.Task; +import ru.oa2.edu.api.domain.task.TaskRepository; + +@Repository +public interface TaskJpaRepository extends JpaRepository, TaskRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/ThemeJpaRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/ThemeJpaRepository.java new file mode 100644 index 0000000..186ac7a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/ThemeJpaRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.application.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.oa2.edu.api.domain.theme.Theme; +import ru.oa2.edu.api.domain.theme.ThemeRepository; + +@Repository +public interface ThemeJpaRepository extends JpaRepository, ThemeRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserJpaRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserJpaRepository.java new file mode 100644 index 0000000..5627fd2 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserJpaRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.application.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.oa2.edu.api.domain.user.User; +import ru.oa2.edu.api.domain.user.UserRepository; + +@Repository +public interface UserJpaRepository extends JpaRepository, UserRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserProgressJpaRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserProgressJpaRepository.java new file mode 100644 index 0000000..43fbb22 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/database/UserProgressJpaRepository.java @@ -0,0 +1,8 @@ +package ru.oa2.edu.api.application.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.oa2.edu.api.domain.user.UserProgress; +import ru.oa2.edu.api.domain.user.UserProgressRepository; + +public interface UserProgressJpaRepository extends JpaRepository, UserProgressRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RepositoryService.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RepositoryService.java new file mode 100644 index 0000000..5c62e82 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RepositoryService.java @@ -0,0 +1,141 @@ +package ru.oa2.edu.api.application.services; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.oa2.edu.api.application.database.*; +import ru.oa2.edu.api.application.utils.TaskComparator; +import ru.oa2.edu.api.application.utils.ThemeComparator; +import ru.oa2.edu.api.domain.task.TaskDTO; +import ru.oa2.edu.api.domain.theme.Theme; +import ru.oa2.edu.api.domain.theme.ThemeDTO; +import ru.oa2.edu.api.domain.user.StatJob; +import ru.oa2.edu.api.domain.user.UserDataStatistic; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Service +@Transactional +public class RepositoryService { + + private final ThemeJpaRepository themeRepository; + private final TaskJpaRepository taskRepository; + private final UserJpaRepository userRepository; + private final UserProgressJpaRepository userProgressRepository; + private final JobJpaRepository jobJpaRepository; + + public RepositoryService(ThemeJpaRepository themeRepository, + TaskJpaRepository taskRepository, + UserJpaRepository userRepository, + UserProgressJpaRepository userProgressRepository, JobJpaRepository jobJpaRepository) { + this.themeRepository = themeRepository; + this.taskRepository = taskRepository; + this.userRepository = userRepository; + this.userProgressRepository = userProgressRepository; + this.jobJpaRepository = jobJpaRepository; + } + + public Collection listThemes() { + var themes = themeRepository.findAll(); + var list = new ArrayList(); + for (var theme : themes) { + list.add( + ThemeDTO.builder() + .id(theme.getId()) + .name(theme.getName()) + .active(theme.isActive()) + .created(theme.getCreated()) + .updated(theme.getUpdated()) + .tasks(null) + .build() + ); + } + list.sort(new ThemeComparator()); + return list; + } + + public ThemeDTO getTheme(Long id) { + ThemeDTO themeDTO = null; + var result = themeRepository.findById(id); + if (result.isPresent()) { + var theme = result.get(); + Collection tasks = new ArrayList<>(); + for (var task : theme.getTasks()) { + tasks.add( + TaskDTO.builder() + .id(task.getId()) + .name(task.getName()) + .active(task.isActive()) + .data(task.getData()) + .created(task.getCreated()) + .updated(task.getUpdated()) + .build() + ); + } + tasks = tasks.stream().sorted(new TaskComparator()).toList(); + themeDTO = ThemeDTO.builder() + .id(theme.getId()) + .name(theme.getName()) + .active(theme.isActive()) + .created(theme.getCreated()) + .updated(theme.getUpdated()) + .tasks(tasks) + .build(); + } + return themeDTO; + } + + public TaskDTO getTask(long id) { + var task = taskRepository.findById(id); + return TaskDTO.builder() + .id(task.getId()) + .name(task.getName()) + .data(task.getData()) + .active(task.isActive()) + .created(task.getCreated()) + .updated(task.getUpdated()) + .build(); + } + + public UserDataStatistic getUserStatistic(long id) { + var stats = new HashMap(); + var stats2 = new HashMap(); + var userData = new UserDataStatistic(); + var result = jobJpaRepository.findUserStatistic(id); + + for (var job : result) { + var date = job.getLastDate().toLocalDateTime().toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + var isDone = job.isResult() ? 1 : 0; + if (stats.containsKey(date)) { + stats.put(date, stats.get(date) + 1); + stats2.put(date, stats2.get(date) + isDone); + } else { + stats.put(date, 1); + stats2.put(date, isDone); + } + } + for (var stat : stats.entrySet()) { + userData.addLabel(stat.getKey()); + userData.addTotal(stat.getValue()); + } + for (var stat : stats2.entrySet()) { + userData.addFinishedCount(stat.getValue()); + } + return userData; + } + + public void createTheme(ThemeDTO themeDTO) { + var theme = new Theme(themeDTO.getName(), themeDTO.isActive()); + themeRepository.save(theme); + } + + public ThemeDTO updateTheme(Long id, ThemeDTO themeDTO) { + // TODO + return null; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerService.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerService.java new file mode 100644 index 0000000..7417d30 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerService.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.application.services; + +import ru.oa2.edu.api.domain.dto.Job; +import ru.oa2.edu.api.domain.dto.JobResult; + +public interface RunnerService { + + long Job(String topicName, Job job); + JobResult Status(long jobId); +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerServiceImpl.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerServiceImpl.java new file mode 100644 index 0000000..f3dc5b0 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/services/RunnerServiceImpl.java @@ -0,0 +1,132 @@ +package ru.oa2.edu.api.application.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import jakarta.transaction.Transactional; +import lombok.extern.log4j.Log4j2; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import ru.oa2.edu.api.application.database.JobJpaRepository; +import ru.oa2.edu.api.application.database.TaskJpaRepository; +import ru.oa2.edu.api.application.database.ThemeJpaRepository; +import ru.oa2.edu.api.application.database.UserJpaRepository; +import ru.oa2.edu.api.domain.dto.DataCase; +import ru.oa2.edu.api.domain.dto.JobResult; +import ru.oa2.edu.api.domain.dto.JobStatus; +import ru.oa2.edu.api.domain.job.Job; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +import static ru.oa2.edu.api.application.utils.MappingTypes.marshalRequestData; + +@Log4j2 +@Service +@Transactional +public class RunnerServiceImpl implements RunnerService { + + private final KafkaTemplate kafkaTemplate; + private final UserJpaRepository userRepository; + private final TaskJpaRepository taskRepository; + private final JobJpaRepository jobRepository; + private final ThemeJpaRepository themeRepository; + + public RunnerServiceImpl(KafkaTemplate kafkaTemplate, + UserJpaRepository userRepository, + TaskJpaRepository taskRepository, + JobJpaRepository jobRepository, + ThemeJpaRepository themeRepository) { + this.kafkaTemplate = kafkaTemplate; + this.userRepository = userRepository; + this.taskRepository = taskRepository; + this.jobRepository = jobRepository; + this.themeRepository = themeRepository; + } + + @Override + public long Job(String topicName, ru.oa2.edu.api.domain.dto.Job jobDTO) { + var mapperObject = new ObjectMapper(); + var user = userRepository.findById(jobDTO.getUserId()); + var task = taskRepository.findById(jobDTO.getTaskId()); + + String requestDataString = null; + try { + requestDataString = marshalRequestData(jobDTO.getRequestData()); + } catch (Exception e) { + log.error(e); + } + var job = new Job(user, task, JobStatus.RUNNING, requestDataString); + jobRepository.save(job); + + var uuid = UUID.randomUUID(); + var requestData = jobDTO.getRequestData(); + var data = task.getData(); + DataCase dataCase = null; + try { + dataCase = mapperObject.readValue(data, DataCase.class); + } catch (JsonProcessingException e) { + //TODO + } + requestData.setContextPath(dataCase.getRequestData().getContextPath()); + var jobResult = new ru.oa2.edu.api.domain.dto.Job( + job.getId(), + job.getUser().getId(), + job.getTask().getId(), + jobDTO.getEngineType(), + requestData, + dataCase.getTestCases() + ); + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + String json = null; + try { + json = ow.writeValueAsString(jobResult); + } catch (JsonProcessingException e) { + // TODO + } + kafkaTemplate.send(topicName, uuid.toString(), json); + // TODO journal message + + return job.getId(); + } + + @Override + public JobResult Status(long jobId) { + var res = jobRepository.findById(jobId); + if (res.isPresent()) { + var job = res.get(); + if (job.getStatus().equals(JobStatus.RUNNING)) { + return null; + } + return new JobResult( + jobId, + job.getTask().getId(), + job.isResult(), + job.getTaskResponse() != null ? Arrays.stream(job.getTaskResponse().split("\n")).toList() : new ArrayList<>(), + null); + } + return null; + } + + @KafkaListener(topics = "edu_results", groupId = "edu") + public void listenGroupFoo(String message) { + System.out.println("Received Message in group foo: " + message); + ObjectMapper mapper = new ObjectMapper(); + JobResult jobResult = null; + try { + jobResult = mapper.readValue(message, JobResult.class); + } catch (JsonProcessingException e) { + log.error(e); + return; + } + var res = jobRepository.findById(jobResult.getJobId()); + // TODO + var job = res.get(); + job.updateResult(jobResult.isStatus(), String.join("\n", jobResult.getLog())); + jobRepository.save(job); + // TODO как аккумулировать статистику (в процессе запроса или при обработке) + // TODO journal message + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/MappingTypes.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/MappingTypes.java new file mode 100644 index 0000000..51d5180 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/MappingTypes.java @@ -0,0 +1,18 @@ +package ru.oa2.edu.api.application.utils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import ru.oa2.edu.api.domain.dto.RequestData; + +public class MappingTypes { + + final static ObjectMapper objectMapper = new ObjectMapper(); + + public static RequestData parseRequestData(Object object) throws Exception { + return objectMapper.readValue((JsonParser) object, RequestData.class); + } + + public static String marshalRequestData(RequestData requestData) throws Exception { + return objectMapper.writeValueAsString(requestData); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/TaskComparator.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/TaskComparator.java new file mode 100644 index 0000000..e4023c0 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/TaskComparator.java @@ -0,0 +1,12 @@ +package ru.oa2.edu.api.application.utils; + +import ru.oa2.edu.api.domain.task.TaskDTO; + +import java.util.Comparator; + +public class TaskComparator implements Comparator { + @Override + public int compare(TaskDTO t1, TaskDTO t2) { + return Long.compare(t1.getId(), t2.getId()); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/ThemeComparator.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/ThemeComparator.java new file mode 100644 index 0000000..c56e70b --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/application/utils/ThemeComparator.java @@ -0,0 +1,12 @@ +package ru.oa2.edu.api.application.utils; + +import ru.oa2.edu.api.domain.theme.ThemeDTO; + +import java.util.Comparator; + +public class ThemeComparator implements Comparator { + @Override + public int compare(ThemeDTO t1, ThemeDTO t2) { + return Long.compare(t1.getId(), t2.getId()); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/DataCase.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/DataCase.java new file mode 100644 index 0000000..9b81293 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/DataCase.java @@ -0,0 +1,34 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +public class DataCase { + + public DataCase() {} + + @JsonProperty("user_id") + private long userId; + + @JsonProperty("description") + private String description; + + @JsonProperty("task_id") + private long taskId; + + @JsonProperty("engine_type") + private String engineType; + + @JsonProperty("request_data") + private RequestData requestData; + + @JsonProperty("test_cases") + private List testCases; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/EngineTypes.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/EngineTypes.java new file mode 100644 index 0000000..567c7c4 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/EngineTypes.java @@ -0,0 +1,25 @@ +package ru.oa2.edu.api.domain.dto; + +public enum EngineTypes { + DOCKER ("docker"), + DOCKER_BUILDX ("docker_buildx"), + KUBERNETES ("kubernetes"); + + private final String name; + private EngineTypes(String name) { + this.name = name; + } + + public String toString() { + return this.name; + } + + public static EngineTypes fromString(String text) { + for (EngineTypes t : EngineTypes.values()) { + if (t.name.equalsIgnoreCase(text)) { + return t; + } + } + return null; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Job.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Job.java new file mode 100644 index 0000000..095a959 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Job.java @@ -0,0 +1,32 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +public class Job { + + @JsonProperty("job_id") + private long jobId; + + @JsonProperty("user_id") + private long userId; + + @JsonProperty("task_id") + private long taskId; + + @JsonProperty("engine_type") + private String engineType; + + @JsonProperty("request_data") + private RequestData requestData; + + @JsonProperty("test_cases") + private List testCases; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobResult.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobResult.java new file mode 100644 index 0000000..a44bfa3 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobResult.java @@ -0,0 +1,29 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +public class JobResult { + + public JobResult() {} + + @JsonProperty("job_id") + private long jobId; + @JsonProperty("task_id") + private long taskId; + @JsonProperty("status") + private boolean status; + + @JsonProperty("log") + private List log; + + @JsonProperty("result") + private String result; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobStatus.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobStatus.java new file mode 100644 index 0000000..e46c215 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/JobStatus.java @@ -0,0 +1,6 @@ +package ru.oa2.edu.api.domain.dto; + +public enum JobStatus { + RUNNING, + FINISHED, +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/RequestData.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/RequestData.java new file mode 100644 index 0000000..e9cccb6 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/RequestData.java @@ -0,0 +1,19 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class RequestData { + + public RequestData() {} + + @JsonProperty("main") + private String main; + @JsonProperty("context_path") + private String contextPath; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Run.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Run.java new file mode 100644 index 0000000..b768984 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/Run.java @@ -0,0 +1,19 @@ +package ru.oa2.edu.api.domain.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.sql.Timestamp; + +@Getter +@Builder +public class Run { + private long id; + private long userId; + private long taskId; + private JobStatus status; + private Object taskRequest; + private String taskResponse; + private boolean result; + private Timestamp lastDate; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/TestCase.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/TestCase.java new file mode 100644 index 0000000..dc64305 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/TestCase.java @@ -0,0 +1,21 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class TestCase { + + public TestCase() {} + + @JsonProperty("case_id") + private long caseId; + @JsonProperty("case_type") + private String caseType; + @JsonProperty("context") + private Object Context; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/UserProgress.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/UserProgress.java new file mode 100644 index 0000000..d65723e --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/dto/UserProgress.java @@ -0,0 +1,30 @@ +package ru.oa2.edu.api.domain.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +@AllArgsConstructor +public class UserProgress { + + public UserProgress() {} + + @JsonProperty("id") + private Long id; + + @JsonProperty("user_id") + private Long userId; + @JsonProperty("theme_id") + private Long themeId; + @JsonProperty("task_id") + private Long taskId; + @JsonProperty("count") + private Long count; + @JsonProperty("done") + private boolean done; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/Job.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/Job.java new file mode 100644 index 0000000..74142e9 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/Job.java @@ -0,0 +1,110 @@ +package ru.oa2.edu.api.domain.job; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import ru.oa2.edu.api.domain.dto.JobStatus; +import ru.oa2.edu.api.domain.dto.RequestData; +import ru.oa2.edu.api.domain.task.Task; +import ru.oa2.edu.api.domain.user.User; + +import java.sql.Timestamp; + +import static ru.oa2.edu.api.application.utils.MappingTypes.parseRequestData; + + +public class Job { + + + private long id; + private User user; + private Task task; + private JobStatus status; + private String taskRequest; + + private String taskResponse; + private boolean result; + private Timestamp lastDate; + + public Job() { + + } + + public Job(User user, Task task, JobStatus status, String taskRequest) { + this.user = user; + this.task = task; + this.status = status; + this.taskRequest = taskRequest; + this.lastDate = new Timestamp(System.currentTimeMillis()); + } + + public void updateResult(boolean result, String taskResponse) { + this.result = result; + this.status = JobStatus.FINISHED; + this.taskResponse = taskResponse; + this.lastDate = new Timestamp(System.currentTimeMillis()); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Task getTask() { + return task; + } + + public void setTask(Task task) { + this.task = task; + } + + public JobStatus getStatus() { + return status; + } + + public void setStatus(JobStatus status) { + this.status = status; + } + + public String getTaskRequest() { + return taskRequest; + } + + public void setTaskRequest(String taskRequest) { + this.taskRequest = taskRequest; + } + + public String getTaskResponse() { + return taskResponse; + } + + public void setTaskResponse(String taskResponse) { + this.taskResponse = taskResponse; + } + + public boolean isResult() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } + + public Timestamp getLastDate() { + return lastDate; + } + + public void setLastDate(Timestamp lastDate) { + this.lastDate = lastDate; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepository.java new file mode 100644 index 0000000..9343ae7 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepository.java @@ -0,0 +1,8 @@ +package ru.oa2.edu.api.domain.job; + +import java.util.List; + +public interface JobRepository { + + List findUserStatistic(long userId); +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepositoryImpl.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepositoryImpl.java new file mode 100644 index 0000000..4075305 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/job/JobRepositoryImpl.java @@ -0,0 +1,21 @@ +package ru.oa2.edu.api.domain.job; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class JobRepositoryImpl implements JobRepository { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public List findUserStatistic(long userId) { + var query = entityManager.createQuery("from Job where user.Id = :userId", Job.class); + query.setParameter("userId", userId); + return query.getResultList(); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/Task.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/Task.java new file mode 100644 index 0000000..b2de2b6 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/Task.java @@ -0,0 +1,62 @@ +package ru.oa2.edu.api.domain.task; + +import java.time.LocalDateTime; + + +public class Task { + + private Long id; + private String name; + private String data; + private boolean active; + private LocalDateTime created; + private LocalDateTime updated; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public LocalDateTime getCreated() { + return created; + } + + public void setCreated(LocalDateTime created) { + this.created = created; + } + + public LocalDateTime getUpdated() { + return updated; + } + + public void setUpdated(LocalDateTime updated) { + this.updated = updated; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskDTO.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskDTO.java new file mode 100644 index 0000000..0190f51 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskDTO.java @@ -0,0 +1,20 @@ +package ru.oa2.edu.api.domain.task; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class TaskDTO { + + private Long id; + private String name; + private String data; + private boolean active; + private LocalDateTime created; + private LocalDateTime updated; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskRepository.java new file mode 100644 index 0000000..a32b14f --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/task/TaskRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.domain.task; + +import java.util.List; + +public interface TaskRepository { + + Task save(Task task); + Task findById(long id); + List findAll(); +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/Theme.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/Theme.java new file mode 100644 index 0000000..f76071a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/Theme.java @@ -0,0 +1,77 @@ +package ru.oa2.edu.api.domain.theme; + + +import ru.oa2.edu.api.domain.task.Task; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + + + +public class Theme { + + private Long id; + private String name; + private boolean active; + private LocalDateTime created; + private LocalDateTime updated; + private Set tasks = new HashSet<>(); + + public Theme() {} + + public Theme(String name, boolean active) { + this.name = name; + this.active = active; + this.created = LocalDateTime.now(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public LocalDateTime getCreated() { + return created; + } + + public void setCreated(LocalDateTime created) { + this.created = created; + } + + public LocalDateTime getUpdated() { + return updated; + } + + public void setUpdated(LocalDateTime updated) { + this.updated = updated; + } + + public Set getTasks() { + return tasks; + } + + public void setTasks(Set tasks) { + this.tasks = tasks; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeDTO.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeDTO.java new file mode 100644 index 0000000..00c5429 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeDTO.java @@ -0,0 +1,25 @@ +package ru.oa2.edu.api.domain.theme; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import ru.oa2.edu.api.domain.task.TaskDTO; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Set; + +@Getter +@Builder +@AllArgsConstructor +public class ThemeDTO { + + public ThemeDTO() {} + + private Long id; + private String name; + private boolean active; + private LocalDateTime created; + private LocalDateTime updated; + private Collection tasks; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeRepository.java new file mode 100644 index 0000000..e34183e --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/theme/ThemeRepository.java @@ -0,0 +1,9 @@ +package ru.oa2.edu.api.domain.theme; + +import java.util.List; + +public interface ThemeRepository { + + Theme findById(long id); + List findAll(); +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/StatJob.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/StatJob.java new file mode 100644 index 0000000..2b5ddff --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/StatJob.java @@ -0,0 +1,13 @@ +package ru.oa2.edu.api.domain.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class StatJob { + private long runs; + private long finish; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/User.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/User.java new file mode 100644 index 0000000..b186a42 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/User.java @@ -0,0 +1,30 @@ +package ru.oa2.edu.api.domain.user; + +public class User { + private Long id; + private String internalId; + + public User() { + + } + + public User(String internalId) { + this.internalId = internalId; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getInternalId() { + return internalId; + } + + public void setInternalId(String internalId) { + this.internalId = internalId; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDTO.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDTO.java new file mode 100644 index 0000000..ed096ef --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDTO.java @@ -0,0 +1,13 @@ +package ru.oa2.edu.api.domain.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class UserDTO { + private String id; + private String internalId; +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDataStatistic.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDataStatistic.java new file mode 100644 index 0000000..a78715f --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserDataStatistic.java @@ -0,0 +1,40 @@ +package ru.oa2.edu.api.domain.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + +@Getter +@Setter +public class UserDataStatistic { + private TreeSet labels; + private TreeSet finishedCount; + private TreeSet total; + private List> series; + + public UserDataStatistic() { + labels = new TreeSet<>(); + total = new TreeSet<>(); + finishedCount = new TreeSet<>(); + } + + public void addLabel(String label) { + labels.add(label); + } + + public void addFinishedCount(int count) { + finishedCount.add(count); + } + + public void addTotal(int count) { + total.add(count); + } + + public List> getSeries() { + return List.of(total, finishedCount); + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserInfo.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserInfo.java new file mode 100644 index 0000000..547fa40 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserInfo.java @@ -0,0 +1,7 @@ +package ru.oa2.edu.api.domain.user; + +import java.util.Map; + +public record UserInfo(String id, String family, String name, String email, UserDataStatistic userStatistic) { + +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgress.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgress.java new file mode 100644 index 0000000..383b5af --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgress.java @@ -0,0 +1,74 @@ +package ru.oa2.edu.api.domain.user; + +import ru.oa2.edu.api.domain.task.Task; +import ru.oa2.edu.api.domain.theme.Theme; + + +public class UserProgress { + + + private Long id; + private User user; + private Theme theme; + private Task task; + private Long count; + private boolean done; + + public UserProgress() {} + + public UserProgress(User user, Theme theme, Task task, Long count, boolean done) { + this.user = user; + this.theme = theme; + this.task = task; + this.count = count; + this.done = done; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Theme getTheme() { + return theme; + } + + public void setTheme(Theme theme) { + this.theme = theme; + } + + public Task getTask() { + return task; + } + + public void setTask(Task task) { + this.task = task; + } + + public Long getCount() { + return count; + } + + public void setCount(Long count) { + this.count = count; + } + + public boolean isDone() { + return done; + } + + public void setDone(boolean done) { + this.done = done; + } +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgressRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgressRepository.java new file mode 100644 index 0000000..f57cb19 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserProgressRepository.java @@ -0,0 +1,4 @@ +package ru.oa2.edu.api.domain.user; + +public interface UserProgressRepository { +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserRepository.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserRepository.java new file mode 100644 index 0000000..2af623a --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserRepository.java @@ -0,0 +1,10 @@ +package ru.oa2.edu.api.domain.user; + +import java.util.List; + +public interface UserRepository { + + User findById(long id); + User findByInternalId(String internalId); + List findAll(); +} diff --git a/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserStatistic.java b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserStatistic.java new file mode 100644 index 0000000..bc51bd9 --- /dev/null +++ b/src/backend/api-server/src/main/java/ru/oa2/edu/api/domain/user/UserStatistic.java @@ -0,0 +1,6 @@ +package ru.oa2.edu.api.domain.user; + +import java.util.Map; + +public record UserStatistic(Map stats) { +} diff --git a/src/backend/api-server/src/main/resources/application.yml b/src/backend/api-server/src/main/resources/application.yml new file mode 100644 index 0000000..9c1ce98 --- /dev/null +++ b/src/backend/api-server/src/main/resources/application.yml @@ -0,0 +1,45 @@ +server: + port: 8082 + +spring: + + application: + name: ApiServer + main: + banner-mode: off + + datasource: + url: jdbc:postgresql://localhost:5432/education_system + username: postgres + password: postgres + + jpa: + profiles: + hibernate: + show_sql: true + format_sql: true + hibernate: + ddl-auto: validate + mapping-resources: + - mappings/job.xml + - mappings/task.xml + - mappings/theme.xml + - mappings/internal_user.xml + - mappings/user_progress.xml + + security: + oauth2: + resourceserver: + jwt: + issuer-uri: http://portal.local:8080/keycloak/realms/edu-portal + +kafka: + address: "127.0.0.1:29092" + +logging: + level: + org: + hibernate: + orm.jdbc.bind: trace + SQL: debug + diff --git a/src/backend/api-server/src/main/resources/liquibase/Dockerfile b/src/backend/api-server/src/main/resources/liquibase/Dockerfile new file mode 100644 index 0000000..4e5c608 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/Dockerfile @@ -0,0 +1,5 @@ +ARG LIQUIBASE_VERSION=4.9 + +FROM liquibase/liquibase:${LIQUIBASE_VERSION} +ADD src/main/resources /liquibase/changelog +ENTRYPOINT ["/liquibase/docker-entrypoint.sh", "--changeLogFile=db.root-master.xml"] diff --git a/src/backend/api-server/src/main/resources/liquibase/Readme.md b/src/backend/api-server/src/main/resources/liquibase/Readme.md new file mode 100644 index 0000000..7b478f9 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/Readme.md @@ -0,0 +1,33 @@ +База данных +=========== + + +Создание структуры БД +--------------------- + +```shell +docker run \ + --network=education-system \ + --volume $PWD/src/main/resources/db:/liquibase/changelog \ + liquibase/liquibase:4.9 \ + --url="jdbc:postgresql://postgres:5432/education_system" \ + --changelog-file=./changelog/db.root-master.xml \ + --username=postgres \ + --password=postgres \ + update +``` + +Создание тестовых данных +------------------------ + +```shell +docker run \ + --network=education-system \ + --volume $PWD/src/main/resources/demo:/liquibase/changelog \ + liquibase/liquibase:4.9 \ + --url="jdbc:postgresql://postgres:5432/education_system" \ + --changelog-file=./changelog/demo.root-master.xml \ + --username=postgres \ + --password=postgres \ + update +``` diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-1-create_theme.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-1-create_theme.xml new file mode 100644 index 0000000..0c77608 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-1-create_theme.xml @@ -0,0 +1,40 @@ + + + + + + + Создание таблицы theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-2-create_list.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-2-create_list.xml new file mode 100644 index 0000000..094e64c --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-2-create_list.xml @@ -0,0 +1,26 @@ + + + + + + + Создание таблицы list + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-3-create_task.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-3-create_task.xml new file mode 100644 index 0000000..479207a --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-3-create_task.xml @@ -0,0 +1,44 @@ + + + + + + + Создание таблицы task + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-4-create_internal_user.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-4-create_internal_user.xml new file mode 100644 index 0000000..4d8af58 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-4-create_internal_user.xml @@ -0,0 +1,28 @@ + + + + + + + Создание таблицы user + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-5-create_user_progress.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-5-create_user_progress.xml new file mode 100644 index 0000000..6a93ff1 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240831-5-create_user_progress.xml @@ -0,0 +1,44 @@ + + + + + + + Создание таблицы user_progress + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-6-create_job.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-6-create_job.xml new file mode 100644 index 0000000..5f2028f --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-6-create_job.xml @@ -0,0 +1,52 @@ + + + + + + + Создание таблицы job + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-7-create_journal.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-7-create_journal.xml new file mode 100644 index 0000000..29f79e5 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.20240919-7-create_journal.xml @@ -0,0 +1,40 @@ + + + + + + + Создание таблицы journal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.version-master.xml b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.version-master.xml new file mode 100644 index 0000000..9e191c2 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/1.0.0/db.version-master.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/db/db.root-master.xml b/src/backend/api-server/src/main/resources/liquibase/db/db.root-master.xml new file mode 100644 index 0000000..c6cf560 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/db/db.root-master.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-1-add_themes.xml b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-1-add_themes.xml new file mode 100644 index 0000000..cec40ba --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-1-add_themes.xml @@ -0,0 +1,39 @@ + + + + + + + Добавление Тем изучения + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-2-add_tasks.xml b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-2-add_tasks.xml new file mode 100644 index 0000000..293a7ea --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-2-add_tasks.xml @@ -0,0 +1,151 @@ + + + + + + + Добавление заданий + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-3-add_lists.xml b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-3-add_lists.xml new file mode 100644 index 0000000..2627252 --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.20240921-3-add_lists.xml @@ -0,0 +1,81 @@ + + + + + + + Связь тем и заданий + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.version-master.xml b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.version-master.xml new file mode 100644 index 0000000..3293dfa --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/demo/1.0.0/demo.version-master.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/liquibase/demo/demo.root-master.xml b/src/backend/api-server/src/main/resources/liquibase/demo/demo.root-master.xml new file mode 100644 index 0000000..f73e82e --- /dev/null +++ b/src/backend/api-server/src/main/resources/liquibase/demo/demo.root-master.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/mappings/internal_user.xml b/src/backend/api-server/src/main/resources/mappings/internal_user.xml new file mode 100644 index 0000000..7117573 --- /dev/null +++ b/src/backend/api-server/src/main/resources/mappings/internal_user.xml @@ -0,0 +1,24 @@ + + + + ru.oa2.edu.api.domain.theme + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/mappings/job.xml b/src/backend/api-server/src/main/resources/mappings/job.xml new file mode 100644 index 0000000..7ea2acc --- /dev/null +++ b/src/backend/api-server/src/main/resources/mappings/job.xml @@ -0,0 +1,49 @@ + + + + ru.oa2.edu.api.domain.theme + + +
+ + + + + + + + + + STRING + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/mappings/task.xml b/src/backend/api-server/src/main/resources/mappings/task.xml new file mode 100644 index 0000000..fd23375 --- /dev/null +++ b/src/backend/api-server/src/main/resources/mappings/task.xml @@ -0,0 +1,40 @@ + + + + ru.oa2.edu.api.domain.theme + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/mappings/theme.xml b/src/backend/api-server/src/main/resources/mappings/theme.xml new file mode 100644 index 0000000..1c1a5d6 --- /dev/null +++ b/src/backend/api-server/src/main/resources/mappings/theme.xml @@ -0,0 +1,50 @@ + + + + ru.oa2.edu.api.domain.theme + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/main/resources/mappings/user_progress.xml b/src/backend/api-server/src/main/resources/mappings/user_progress.xml new file mode 100644 index 0000000..1e7cc66 --- /dev/null +++ b/src/backend/api-server/src/main/resources/mappings/user_progress.xml @@ -0,0 +1,39 @@ + + + ru.oa2.edu.api.domain.theme + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/api-server/src/test/java/ru/oa2/edu/api/ThemeTest.java b/src/backend/api-server/src/test/java/ru/oa2/edu/api/ThemeTest.java new file mode 100644 index 0000000..6133a1d --- /dev/null +++ b/src/backend/api-server/src/test/java/ru/oa2/edu/api/ThemeTest.java @@ -0,0 +1,23 @@ +package ru.oa2.edu.api; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import ru.oa2.edu.api.application.services.RepositoryService; +import ru.oa2.edu.api.domain.theme.ThemeDTO; + +import java.util.Collection; + +@SpringBootTest +public class ThemeTest { + + @Autowired + private RepositoryService repositoryService; + + @Test + public void getListTest() { + Collection listThemes = repositoryService.listThemes(); + // TODO данные для теста + System.out.println(listThemes); + } +} \ No newline at end of file diff --git a/src/backend/case-services/java-http-ok/pom.xml b/src/backend/case-services/java-http-ok/pom.xml new file mode 100644 index 0000000..78471e5 --- /dev/null +++ b/src/backend/case-services/java-http-ok/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + ru.edu.portal + ok-http-server + 1.0.0 + + Edu Portal Ok Service + + + UTF-8 + 21 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + ru.edu.portal.OkHttpServer + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + 21 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + ru.edu.portal.OkHttpServer + + + + + + + + \ No newline at end of file diff --git a/src/backend/case-services/java-http-ok/src/main/java/ru/edu/portal/OkHttpServer.java b/src/backend/case-services/java-http-ok/src/main/java/ru/edu/portal/OkHttpServer.java new file mode 100644 index 0000000..af4c964 --- /dev/null +++ b/src/backend/case-services/java-http-ok/src/main/java/ru/edu/portal/OkHttpServer.java @@ -0,0 +1,56 @@ +package ru.edu.portal; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.util.logging.Logger; + + +public class OkHttpServer { + private final static Logger LOG = Logger.getLogger("NOFRAMEWORKS"); + + public static void main(String[] args) throws IOException { + + final int port = Integer.parseInt(System.getProperty("appPort", "8500")); + + + final HttpServer server = HttpServer.create(new InetSocketAddress(port), 50); + server.createContext("/").setHandler(new Root()); + server.createContext("/health").setHandler(new Health()); + + server.start(); + } + + private static class Root implements HttpHandler { + + public Root() {} + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().add("Content-Type", "text/html"); + exchange.sendResponseHeaders(200, 0); + var responseBody = exchange.getResponseBody(); + responseBody.write("

OK

".getBytes()); + exchange.getResponseBody().close(); + LOG.info(String.format("[GET] / CODE: 200 - %s", exchange.getRequestHeaders().get("User-Agent"))); + } + } + + private static class Health implements HttpHandler { + + public Health() {} + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().add("Content-Type", "text/html"); + exchange.sendResponseHeaders(200, 0); + exchange.getResponseBody().close(); + LOG.info(String.format("[GET] /health CODE: 200 - %s", exchange.getRequestHeaders().get("User-Agent"))); + } + } +} diff --git a/src/backend/engines/docker/.gitignore b/src/backend/engines/docker/.gitignore new file mode 100755 index 0000000..29b636a --- /dev/null +++ b/src/backend/engines/docker/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml \ No newline at end of file diff --git a/src/backend/engines/docker/api.go b/src/backend/engines/docker/api.go new file mode 100644 index 0000000..21818cb --- /dev/null +++ b/src/backend/engines/docker/api.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "log" +) + +type ApiServer struct { + router *gin.Engine +} + +func NewApiServer() *ApiServer { + r := gin.Default() + router(r) + return &ApiServer{r} +} + +func (s *ApiServer) Run(addr string) { + err := s.router.Run(addr) + if err != nil { + log.Fatal(err) + } +} + +func router(router *gin.Engine) { + //TODO добавить проверки запуска, возвращать разные коды + router.GET("/health", func(c *gin.Context) { + c.JSON(200, gin.H{ + "status": "UP", + }) + }) +} diff --git a/src/backend/engines/docker/build.go b/src/backend/engines/docker/build.go new file mode 100755 index 0000000..51281f6 --- /dev/null +++ b/src/backend/engines/docker/build.go @@ -0,0 +1,125 @@ +package main + +import ( + "archive/tar" + "bytes" + "github.com/docker/docker/api/types" + "io" + "os" + "path/filepath" +) + +type BuildRequest struct { + DockerfileName string + ImageName string + types.BuilderVersion + dockerfile string + contextPath string +} + +func (c *Client) Build(buildRequest BuildRequest) (*string, error) { + + dockerFileTarReader, err := loadContext(buildRequest) + + imageBuildResponse, err := c.cli.ImageBuild( + c.ctx, + dockerFileTarReader, + types.ImageBuildOptions{ + Tags: []string{buildRequest.ImageName}, + Context: dockerFileTarReader, + Dockerfile: buildRequest.DockerfileName, + Version: buildRequest.BuilderVersion, + Remove: true}) + + if err != nil { + return nil, err + } + defer imageBuildResponse.Body.Close() + + logBuffer := new(bytes.Buffer) + _, err = io.Copy(logBuffer, imageBuildResponse.Body) + if err != nil { + return nil, err + } + log := logBuffer.String() + + return &log, nil +} + +func loadContext(buildRequest BuildRequest) (*bytes.Reader, error) { + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + + err := addDockerfile(buildRequest, tw) + if err != nil { + return nil, err + } + + err = addContextFiles(buildRequest.contextPath, tw) + if err != nil { + return nil, err + } + + return bytes.NewReader(buf.Bytes()), nil +} + +func addDockerfile(buildRequest BuildRequest, tarWriter *tar.Writer) error { + readDockerFile := []byte(buildRequest.dockerfile) + + tarHeader := &tar.Header{ + Name: buildRequest.DockerfileName, + Size: int64(len(readDockerFile)), + } + + err := tarWriter.WriteHeader(tarHeader) + if err != nil { + return err + } + _, err = tarWriter.Write(readDockerFile) + if err != nil { + return err + } + return nil +} + +func addContextFiles(filePath string, tarWriter *tar.Writer) error { + err := filepath.Walk(filePath, + func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + err = addFile(path, tarWriter) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + return nil +} + +func addFile(filename string, tarWriter *tar.Writer) error { + body, err := os.ReadFile(filename) + if err != nil { + return err + } + fileBase := filepath.Base(filename) + tarHeader := &tar.Header{ + Name: fileBase, + Size: int64(len(body)), + } + err = tarWriter.WriteHeader(tarHeader) + if err != nil { + return err + } + _, err = tarWriter.Write(body) + if err != nil { + return err + } + + return nil +} diff --git a/src/backend/engines/docker/build_test.go b/src/backend/engines/docker/build_test.go new file mode 100755 index 0000000..9ab164a --- /dev/null +++ b/src/backend/engines/docker/build_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/docker/docker/api/types" + "log" + "strings" + "testing" + "time" +) + +const ( + df = ` +FROM python:3.8-alpine +COPY ./requirements.txt /app/requirements.txt +WORKDIR /app +RUN pip install -r requirements.txt +COPY . /app +ENTRYPOINT [ "python" ] + +CMD ["main.py" ] +` +) + +func TestBuild(t *testing.T) { + client := NewClient() + + result, err := client.Build( + BuildRequest{ + DockerfileName: "Dockerfile", + ImageName: fmt.Sprintf("image-%v-%s:latest", 1, time.Now().Format("20060102030405")), + BuilderVersion: types.BuilderV1, + dockerfile: df, + contextPath: "./contexts/case1/", + }) + if err != nil { + t.Error(err) + } + for _, line := range strings.Split(*result, "\n") { + if strings.HasPrefix(line, "{") { + newLine := LogLine{} + err := json.Unmarshal([]byte(line), &newLine) + if err != nil { + log.Printf("ERR: %s\n", err.Error()) + log.Printf("Line: \n%s\n", line) + } + if newLine.Error != nil { + t.Errorf("BUILD ERR: %s\n", *newLine.Error) + } + } + } +} + +func TestName(t *testing.T) { + name := fmt.Sprintf("image-%v-%s:latest", 1, time.Now().Format("20060102030405")) + t.Log(name) + t.Fail() +} diff --git a/src/backend/engines/docker/client.go b/src/backend/engines/docker/client.go new file mode 100755 index 0000000..47dd483 --- /dev/null +++ b/src/backend/engines/docker/client.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "github.com/docker/docker/client" +) + +type Client struct { + cli *client.Client + ctx context.Context +} + +func NewClient() *Client { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + panic(err) + } + client := Client{ + cli, + context.Background(), + } + return &client +} diff --git a/src/backend/engines/docker/containers.go b/src/backend/engines/docker/containers.go new file mode 100755 index 0000000..031a1df --- /dev/null +++ b/src/backend/engines/docker/containers.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "log" +) + +type StartRequest struct { + ImageName string +} + +func (c *Client) StartContainer(startRequest StartRequest) (*string, error) { + + containerResponse, err := c.cli.ContainerCreate( + c.ctx, + &container.Config{ + Image: startRequest.ImageName, + Tty: false}, + &container.HostConfig{}, + &network.NetworkingConfig{}, + nil, + "") + + if err != nil { + return nil, err + } + + log.Printf("Start container ID: %s\n", containerResponse.ID) + + err = c.cli.ContainerStart(c.ctx, containerResponse.ID, container.StartOptions{}) + if err != nil { + log.Printf("ERROR: %s\n", err.Error()) + } + return &containerResponse.ID, nil +} diff --git a/src/backend/engines/docker/containers_test.go b/src/backend/engines/docker/containers_test.go new file mode 100755 index 0000000..f3f8259 --- /dev/null +++ b/src/backend/engines/docker/containers_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "github.com/docker/docker/api/types/container" + "testing" + "time" +) + +func TestStartContainer(t *testing.T) { + client := NewClient() + + id, err := client.StartContainer(StartRequest{ImageName: "flask-app:latest"}) + if err != nil { + t.Error(err) + } + t.Log(id) + + //TODO wait container ??? + time.Sleep(3 * time.Second) + + caseTest := TestCase{ + CaseId: 1, + CaseType: "request", + Context: ContextRequest{ + 5000, + "/hello", + "", + "GET", + 200, + "Hi, Bro!", + }, + } + status, err := client.TestRun(*id, caseTest) + if !status { + t.Error(err) + } + + err = client.cli.ContainerStop(client.ctx, *id, container.StopOptions{}) + if err != nil { + t.Error(err) + } + err = client.cli.ContainerRemove(client.ctx, *id, container.RemoveOptions{}) + if err != nil { + t.Error(err) + } +} + +func TestContextContainer(t *testing.T) { + client := NewClient() + + time.Sleep(3 * time.Second) + + caseTest := TestCase{ + CaseId: 2, + CaseType: "context", + Context: ContextTest{ + "$.Size", + "<", + "60000000", + }, + } + status, err := client.TestRun("flask-app:latest", caseTest) + t.Log("status", status, err) + if !status { + t.Error(err) + } +} diff --git a/src/backend/engines/docker/contexts/base/case1/app.jar b/src/backend/engines/docker/contexts/base/case1/app.jar new file mode 100644 index 0000000..83b7e4c Binary files /dev/null and b/src/backend/engines/docker/contexts/base/case1/app.jar differ diff --git a/src/backend/engines/docker/contexts/base/case1/request.json b/src/backend/engines/docker/contexts/base/case1/request.json new file mode 100644 index 0000000..8a617e3 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case1/request.json @@ -0,0 +1,19 @@ +{ + "job_id": 1, + "engine_type": "docker", + "request_data": { + "main": "FROM eclipse-temurin:17-jre-alpine\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar", + "context_path": "./contexts/base/case1/" + }, + "test_cases": [ + { + "case_id": 1, + "case_type": "context", + "context": { + "Field": "$.Size", + "Operator": "<", + "Value": "209715200" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case2/app.jar b/src/backend/engines/docker/contexts/base/case2/app.jar new file mode 100644 index 0000000..83b7e4c Binary files /dev/null and b/src/backend/engines/docker/contexts/base/case2/app.jar differ diff --git a/src/backend/engines/docker/contexts/base/case2/request.json b/src/backend/engines/docker/contexts/base/case2/request.json new file mode 100644 index 0000000..bac4ba9 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case2/request.json @@ -0,0 +1,19 @@ +{ + "job_id": 1, + "engine_type": "docker", + "request_data": { + "main": "FROM openjdk:17\n\nLABEL org.opencontainers.image.authors=\"Anton Dzyk\"\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar", + "context_path": "./contexts/base/case2/" + }, + "test_cases": [ + { + "case_id": 1, + "case_type": "context", + "context": { + "Field": "$.Config.Labels", + "Operator": "=", + "Value": "org.opencontainers.image.authors=Anton Dzyk" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case3/main.py b/src/backend/engines/docker/contexts/base/case3/main.py new file mode 100755 index 0000000..331a98c --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case3/main.py @@ -0,0 +1,10 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/hello') +def hello(): + return 'Hello!' + +if __name__ == '__main__': + app.run(host="0.0.0.0") \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case3/request.json b/src/backend/engines/docker/contexts/base/case3/request.json new file mode 100644 index 0000000..2ede1e3 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case3/request.json @@ -0,0 +1,22 @@ +{ + "job_id": 1, + "engine_type": "docker_buildx", + "request_data": { + "main": "FROM python:3.8-alpine\nADD https://gitflic.ru/project/dzyk/demo-flask.git /app\nWORKDIR /app\nRUN pip install -r requirements.txt\nCOPY . /app\nENTRYPOINT [\"python\"]\nCMD [\"main.py\"]", + "context_path": "./contexts/base/case2/" + }, + "test_cases": [ + { + "case_id": 1, + "case_type": "request", + "context": { + "container_port": 5000, + "url_path": "/hello", + "request_body": "", + "type_request": "GET", + "expected_code": 200, + "expected_body": "Hello!" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case3/requirements.txt b/src/backend/engines/docker/contexts/base/case3/requirements.txt new file mode 100755 index 0000000..c204bef --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case3/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.0 \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case4/Dockerfile b/src/backend/engines/docker/contexts/base/case4/Dockerfile new file mode 100644 index 0000000..fbddf30 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case4/Dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:21 + +ADD app.jar /opt + +WORKDIR /opt + +CMD ["/usr/bin/java", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case4/app.jar b/src/backend/engines/docker/contexts/base/case4/app.jar new file mode 100644 index 0000000..83b7e4c Binary files /dev/null and b/src/backend/engines/docker/contexts/base/case4/app.jar differ diff --git a/src/backend/engines/docker/contexts/base/case4/request.json b/src/backend/engines/docker/contexts/base/case4/request.json new file mode 100644 index 0000000..def1ad4 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case4/request.json @@ -0,0 +1,22 @@ +{ + "job_id": 1, + "engine_type": "docker", + "request_data": { + "main": "FROM openjdk:21\n\nADD app.jar /opt\n\nWORKDIR /opt\n\nCMD [\"/usr/bin/java\", \"-jar\", \"app.jar\"]", + "context_path": "./contexts/base/case4/" + }, + "test_cases": [ + { + "case_id": 1, + "case_type": "request", + "context": { + "container_port": 8500, + "url_path": "/health", + "request_body": "", + "type_request": "GET", + "expected_code": 200, + "expected_body": "" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case5/Dockerfile b/src/backend/engines/docker/contexts/base/case5/Dockerfile new file mode 100644 index 0000000..664115b --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case5/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:1.22.5 AS build +WORKDIR /go/src/ +COPY . . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o case5 case5.go + +FROM scratch + +COPY --from=build /go/src/case5 . + +ENTRYPOINT ["/case5"] diff --git a/src/backend/engines/docker/contexts/base/case5/case5.go b/src/backend/engines/docker/contexts/base/case5/case5.go new file mode 100644 index 0000000..6095c7b --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case5/case5.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "log" + "net/http" +) + +func main() { + http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + _, err := fmt.Fprintf(w, "pong") + if err != nil { + log.Printf("ERR: %v", err) + } + }) + + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/src/backend/engines/docker/contexts/base/case5/request.json b/src/backend/engines/docker/contexts/base/case5/request.json new file mode 100644 index 0000000..e9d19a1 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case5/request.json @@ -0,0 +1,22 @@ +{ + "job_id": 1, + "engine_type": "docker", + "request_data": { + "main": "FROM golang:1.22.5 AS build\nWORKDIR /go/src/\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=\"-w -s\" -o case5 case5.go\n\nFROM scratch\n\nCOPY --from=build /go/src/case5 .\n\nENTRYPOINT [\"/case5\"]", + "context_path": "./contexts/base/case5/" + }, + "test_cases": [ + { + "case_id": 1, + "case_type": "request", + "context": { + "container_port": 8080, + "url_path": "/ping", + "request_body": "", + "type_request": "GET", + "expected_code": 200, + "expected_body": "pong" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/contexts/base/case_1_back.json b/src/backend/engines/docker/contexts/base/case_1_back.json new file mode 100644 index 0000000..61c1b38 --- /dev/null +++ b/src/backend/engines/docker/contexts/base/case_1_back.json @@ -0,0 +1,26 @@ +{ + "task_id": 1, + "job_id": 1, + "engine_type": "docker", + "description": "В отделе был разработан сервис на Java. Теперь нужно опубликовать его на тестовом стенде, на котором используется Kubernetes. Чтобы его опубликовать - необходимо запаковать его в Docker-контейнер. Вам неоходимо выбрать базовый образ для будущего образа. Но есть условие, чтобы размер образа не был больше 100Mb", + "examples": { + "main": "FROM ...\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar" + }, + "tags": [ + "docker", + "FROM" + ], + "author": "Anton Dzyk", + "task_template": null, + "test_cases": [ + { + "case_id": 1, + "case_type": "context", + "context": { + "Field": "$.Size", + "Operator": "<", + "Value": "107374182400" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/engines/docker/go.mod b/src/backend/engines/docker/go.mod new file mode 100755 index 0000000..a414ee2 --- /dev/null +++ b/src/backend/engines/docker/go.mod @@ -0,0 +1,72 @@ +module docker-engine + +go 1.21.0 + +toolchain go1.22.5 + +require ( + github.com/PaesslerAG/jsonpath v0.1.1 + github.com/docker/docker v27.2.1+incompatible + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/moby/buildkit v0.16.0 + github.com/twmb/franz-go v1.17.1 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/PaesslerAG/gval v1.0.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/twmb/franz-go/pkg/kmsg v1.8.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/grpc v1.62.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect +) diff --git a/src/backend/engines/docker/go.sum b/src/backend/engines/docker/go.sum new file mode 100755 index 0000000..7264aa2 --- /dev/null +++ b/src/backend/engines/docker/go.sum @@ -0,0 +1,206 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8= +github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= +github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= +github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= +github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI= +github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE= +github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/twmb/franz-go v1.17.1 h1:0LwPsbbJeJ9R91DPUHSEd4su82WJWcTY1Zzbgbg4CeQ= +github.com/twmb/franz-go v1.17.1/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= +github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= +github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/src/backend/engines/docker/image.go b/src/backend/engines/docker/image.go new file mode 100644 index 0000000..698bce6 --- /dev/null +++ b/src/backend/engines/docker/image.go @@ -0,0 +1,11 @@ +package main + +import "github.com/docker/docker/api/types" + +func (c *Client) ImageContent(name string) (*types.ImageInspect, error) { + inspect, _, err := c.cli.ImageInspectWithRaw(c.ctx, name) + if err != nil { + return nil, err + } + return &inspect, nil +} diff --git a/src/backend/engines/docker/kafka_client.go b/src/backend/engines/docker/kafka_client.go new file mode 100644 index 0000000..23b9a3a --- /dev/null +++ b/src/backend/engines/docker/kafka_client.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/google/uuid" + "github.com/twmb/franz-go/pkg/kgo" + "log" +) + +type KafkaClient struct { + client *kgo.Client + ctx context.Context +} + +func NewKafkaClient() *KafkaClient { + + seeds := []string{"127.0.0.1:29092"} + + cl, err := kgo.NewClient( + kgo.SeedBrokers(seeds...), + kgo.ConsumerGroup("edu-group"), + kgo.ConsumeTopics("edu_task_docker"), + ) + if err != nil { + panic(err) + } + + return &KafkaClient{ + cl, + context.Background(), + } + +} + +func (kc *KafkaClient) Consume() { + + for { + fetches := kc.client.PollFetches(kc.ctx) + if errs := fetches.Errors(); len(errs) > 0 { + panic(fmt.Sprint(errs)) + } + + fetches.EachPartition(func(p kgo.FetchTopicPartition) { + p.EachRecord(func(record *kgo.Record) { + task := &Task{} + err := json.Unmarshal(record.Value, &task) + if err != nil { + log.Printf("ERROR: %v", err.Error()) + } + TaskRun(task) + }) + }) + } +} + +func (kc *KafkaClient) Produce(message []byte) { + id := uuid.New().String() + record := &kgo.Record{Topic: "edu_results", Key: []byte(id), Value: message} + + if err := kc.client.ProduceSync(kc.ctx, record).FirstErr(); err != nil { + log.Printf("record had a produce error while synchronously producing: %v\n", err) + } +} + +func (kc *KafkaClient) Close() { + kc.client.Close() +} diff --git a/src/backend/engines/docker/main.go b/src/backend/engines/docker/main.go new file mode 100755 index 0000000..1f6020d --- /dev/null +++ b/src/backend/engines/docker/main.go @@ -0,0 +1,21 @@ +package main + +import "log" + +var ( + apiServer *ApiServer + KafkaCurrentClient *KafkaClient +) + +func main() { + log.Println("Start...") + apiServer = NewApiServer() + KafkaCurrentClient = NewKafkaClient() + + go func() { + defer KafkaCurrentClient.Close() + KafkaCurrentClient.Consume() + }() + + apiServer.Run(":8083") +} diff --git a/src/backend/engines/docker/network.go b/src/backend/engines/docker/network.go new file mode 100755 index 0000000..3493db0 --- /dev/null +++ b/src/backend/engines/docker/network.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/docker/docker/api/types" + "log" +) + +func (c *Client) CreateNetwork() { + networkResponse, err := c.cli.NetworkCreate(c.ctx, "test-network", types.NetworkCreate{Driver: "bridge"}) + if err != nil { + log.Printf("error: %s", err.Error()) + } + log.Printf("Create network ID: %s\n", networkResponse.ID) +} + +func (c *Client) ListNetwork() { + networkList, _ := c.cli.NetworkList(c.ctx, types.NetworkListOptions{}) + log.Println("Network List:") + for number, network := range networkList { + log.Printf("# %d, Name: %s", number, network.Name) + } +} diff --git a/src/backend/engines/docker/prune.go b/src/backend/engines/docker/prune.go new file mode 100755 index 0000000..ec4d52e --- /dev/null +++ b/src/backend/engines/docker/prune.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "log" +) + +func (c *Client) Prune() { + log.Println("Start Prune") + + log.Println("Cache:") + report, _ := c.cli.BuildCachePrune(c.ctx, types.BuildCachePruneOptions{All: true}) + for _, nameCache := range report.CachesDeleted { + log.Printf("Name: %s\n", nameCache) + } + + log.Println("Containers:") + pruneReport, _ := c.cli.ContainersPrune(c.ctx, filters.Args{}) + for _, pruneContainer := range pruneReport.ContainersDeleted { + log.Printf("Name: %s\n", pruneContainer) + } + + log.Println("Volumes:") + pruneVolumes, _ := c.cli.VolumesPrune(c.ctx, filters.Args{}) + for _, pruneVolume := range pruneVolumes.VolumesDeleted { + log.Printf("Name: %s\n", pruneVolume) + } + + log.Println("Images:") + pruneImages, _ := c.cli.ImagesPrune(c.ctx, filters.Args{}) + for _, pruneImage := range pruneImages.ImagesDeleted { + log.Printf("Name: %s\n", pruneImage) + } + + log.Printf("Prune finish") +} diff --git a/src/backend/engines/docker/show_env.go b/src/backend/engines/docker/show_env.go new file mode 100755 index 0000000..fb0a546 --- /dev/null +++ b/src/backend/engines/docker/show_env.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "github.com/docker/docker/client" + "log" +) + +func (c *Client) PrintEnv() { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + panic(err) + } + + log.Printf("API Version: %s\n", cli.ClientVersion()) + ctx := context.Background() + p, err := cli.Ping(ctx) + if err != nil { + log.Printf("Ping Docker Engine error: %s\n", err.Error()) + } + log.Printf("API Version: %s, OSType: %s, Experimental: %t, BuilderVersion: %s\n", + p.APIVersion, p.OSType, p.Experimental, p.BuilderVersion) + + info, _ := cli.Info(ctx) + log.Printf("INFO\n CPUSet: %t, kernel version: %s\n", info.CPUSet, info.KernelVersion) + + log.Printf("Host address: %s\n", cli.DaemonHost()) + + /* + log.Printf("\n# Volumes #\n") + vList, _ := cli.VolumeList(ctx, volume.ListOptions{}) + for number, nameVolume := range vList.Volumes { + log.Printf("# %d - Name: %s", number, nameVolume.Name) + } + */ +} diff --git a/src/backend/engines/docker/struct.go b/src/backend/engines/docker/struct.go new file mode 100644 index 0000000..aa9d081 --- /dev/null +++ b/src/backend/engines/docker/struct.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/json" +) + +type Task struct { + JobId int64 `json:"job_id"` + EngineType string `json:"engine_type"` + RequestData map[string]string `json:"request_data"` + TestCases []TestCase `json:"test_cases"` +} + +type TaskResult struct { + Status bool `json:"status"` + Log []string `json:"log"` + Result string `json:"result"` + JobId int64 `json:"job_id"` +} + +type TestCase struct { + CaseId int64 `json:"case_id"` + CaseType string `json:"case_type"` + Context interface{} `json:"context"` +} + +func (c *TestCase) getContextRequest() ContextRequest { + requestBytes, _ := json.Marshal(c.Context) + contextRequest := ContextRequest{} + json.Unmarshal(requestBytes, &contextRequest) + return contextRequest +} + +func (c *TestCase) getContextContext() ContextTest { + caseBytes, _ := json.Marshal(c.Context) + caseTest := ContextTest{} + json.Unmarshal(caseBytes, &caseTest) + return caseTest +} + +type ContextRequest struct { + ContainerPort int `json:"container_port"` + UrlPath string `json:"url_path"` + RequestBody string `json:"request_body"` + TypeRequest string `json:"type_request"` + ExpectedCode int `json:"expected_code"` + ExpectedBody string `json:"expected_body"` +} + +type ContextTest struct { + Field string `json:"field"` + Operator string `json:"operator"` + Value string `json:"value"` +} + +type LogLine struct { + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + *Aux `json:"aux,omitempty"` + Error *string `json:"error,omitempty"` + ErrorDetail *ErrorMessage `json:"errorMessage,omitempty"` +} + +type Aux struct { + ID string +} + +type ErrorMessage struct { + Message string `json:"message,omitempty"` +} + +type LogLine2 struct { + Id string `json:"id"` + Aux interface{} `json:"aux"` +} diff --git a/src/backend/engines/docker/struct_test.go b/src/backend/engines/docker/struct_test.go new file mode 100644 index 0000000..ab29986 --- /dev/null +++ b/src/backend/engines/docker/struct_test.go @@ -0,0 +1,22 @@ +package main + +import "testing" + +func TestTestCaseContextCast(t *testing.T) { + + var c interface{} = map[string]interface{}{ + "field": "$.xxx", + "operator": "<", + "value": "Test", + } + + test := TestCase{ + 1, + "docker", + c} + + res := test.getContextContext() + if res.Value != "Test" { + t.Fail() + } +} diff --git a/src/backend/engines/docker/task_runner.go b/src/backend/engines/docker/task_runner.go new file mode 100644 index 0000000..af25740 --- /dev/null +++ b/src/backend/engines/docker/task_runner.go @@ -0,0 +1,139 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/docker/docker/api/types" + controlapi "github.com/moby/buildkit/api/services/control" + "log" + "strings" + "time" +) + +func TaskRun(task *Task) { + client := NewClient() + + imageId := fmt.Sprintf("%v-%s:latest", task.JobId, time.Now().Format("20060102030405")) + result, err := client.Build(BuildRequest{ + DockerfileName: "Dockerfile", + ImageName: imageId, + BuilderVersion: getBuildVersion(task.EngineType), + dockerfile: task.RequestData["main"], + contextPath: task.RequestData["context_path"], + }) + if err != nil { + KafkaCurrentClient.Produce(TaskResultBody(task.JobId, false, err.Error(), nil)) + return + } + + errorMessages := make([]string, 0) + logMessage := make([]string, 0) + lastError := "" + + for _, line := range strings.Split(*result, "\n") { + if strings.HasPrefix(line, "{") { + ln, err := parseLog(line, getBuildVersion(task.EngineType)) + if err != nil { + errorMessages = append(errorMessages, fmt.Sprintf("ERR: %s\n", err.Error())) + lastError = err.Error() + } + if ln != nil { + logMessage = append(logMessage, fmt.Sprintf("%s\n", *ln)) + } + } + } + + if len(errorMessages) > 0 { + KafkaCurrentClient.Produce(TaskResultBody(task.JobId, false, lastError, errorMessages)) + return + } + + logMessage = append(logMessage, "\n\nRun Tests:\n") + finalStatus := true + for _, testCase := range task.TestCases { + status, err := client.TestRun(imageId, testCase) + if !status { + finalStatus = false + } + logMessage = append(logMessage, fmt.Sprintf("CaseId: %v, Result: %v, Err: %v", testCase.CaseId, status, err)) + } + KafkaCurrentClient.Produce(TaskResultBody(task.JobId, finalStatus, "", logMessage)) +} + +func TaskResultBody(jobId int64, status bool, errorMsg string, logMessages []string) []byte { + res := TaskResult{ + status, + logMessages, + errorMsg, + jobId, + } + + body, err := json.Marshal(res) + if err != nil { + log.Printf("ERR: %s\n", err.Error()) + } + return body +} + +func getBuildVersion(buildVersion string) types.BuilderVersion { + if buildVersion == "docker" { + return types.BuilderV1 + } + if buildVersion == "docker_buildx" { + return types.BuilderBuildKit + } + return types.BuilderV1 +} + +func parseLog(line string, version types.BuilderVersion) (*string, error) { + if version == types.BuilderV1 { + newLine := LogLine{} + err := json.Unmarshal([]byte(line), &newLine) + if err != nil { + return nil, err + } else { + if newLine.Error != nil { + return nil, fmt.Errorf(*newLine.Error) + } + return &newLine.Stream, nil + } + } + if version == types.BuilderBuildKit { + // TODO использоваться парсер из кода moby + newLine := LogLine2{} + err := json.Unmarshal([]byte(line), &newLine) + if err != nil { + return nil, err + } + + if newLine.Id == "moby.buildkit.trace" { + data, err := base64.StdEncoding.DecodeString(newLine.Aux.(string)) + aux := controlapi.StatusResponse{} + err = aux.Unmarshal(data) + if err != nil { + return nil, err + } else { + listDigest := make([]string, 0) + message := "" + for _, ln := range aux.Vertexes { + if ln.Completed != nil { + repeat := false + for _, dg := range listDigest { + if ln.Digest.Hex() == dg { + repeat = true + } + } + if repeat { + continue + } + listDigest = append(listDigest, ln.Digest.Hex()) + message = message + "\n" + ln.Name + } + } + return &message, nil + } + } + } + return nil, nil +} diff --git a/src/backend/engines/docker/test_engine.go b/src/backend/engines/docker/test_engine.go new file mode 100755 index 0000000..aeb2679 --- /dev/null +++ b/src/backend/engines/docker/test_engine.go @@ -0,0 +1,145 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/docker/docker/api/types/container" + "io" + "log" + "net/http" + "strconv" + "strings" + "time" + + jsonpath "github.com/PaesslerAG/jsonpath" +) + +func (c *Client) TestRun(ID string, testcase TestCase) (bool, error) { + switch testcase.CaseType { + case "request": + return c.curlCase(ID, testcase) + case "context": + return c.contextCase(ID, testcase) + default: + return false, fmt.Errorf("not support case type") + } +} + +func (c *Client) curlCase(ID string, testcase TestCase) (bool, error) { + + id, err := c.StartContainer(StartRequest{ImageName: ID}) + if err != nil { + return false, err + } + defer c.stopContainer(id) + + //TODO wait container ??? + time.Sleep(3 * time.Second) + + info, err := c.cli.ContainerInspect(c.ctx, *id) + if err != nil { + return false, err + } + log.Printf("Get info container %s, IP:%s\n", info.Name, info.NetworkSettings.IPAddress) + var IPAddress = info.NetworkSettings.IPAddress + + contextCase := testcase.getContextRequest() + + body := strings.NewReader(contextCase.RequestBody) + request, err := http.NewRequest(contextCase.TypeRequest, fmt.Sprintf("http://%s:%d%s", + IPAddress, contextCase.ContainerPort, contextCase.UrlPath), body) + if err != nil { + return false, err + } + resp, err := http.DefaultClient.Do(request) + if err != nil { + return false, err + } + + if resp.StatusCode != contextCase.ExpectedCode { + return false, fmt.Errorf("Response CODE does not match\nResult: %d\nExpected: %d", + resp.StatusCode, contextCase.ExpectedCode) + } + + if contextCase.ExpectedBody != "" { + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, err + } + strBody := string(body) + if strBody != contextCase.ExpectedBody { + return false, fmt.Errorf("Response body does not match\nResult: %s\nExpected: %s", + strBody, contextCase.ExpectedBody) + } + } + + return true, nil +} + +func (c *Client) stopContainer(ID *string) { + _ = c.cli.ContainerStop(c.ctx, *ID, container.StopOptions{}) + _ = c.cli.ContainerRemove(c.ctx, *ID, container.RemoveOptions{}) +} + +func (c *Client) contextCase(ID string, testcase TestCase) (bool, error) { + inspect, err := c.ImageContent(ID) + if err != nil { + return false, err + } + contextTest := testcase.getContextContext() + + body, err := json.Marshal(inspect) + if err != nil { + return false, err + } + bodyObject := interface{}(nil) + err = json.Unmarshal(body, &bodyObject) + if err != nil { + return false, err + } + result, err := jsonpath.Get(contextTest.Field, bodyObject) + if err != nil { + return false, err + } + + switch contextTest.Operator { + case "<": + res := lessInt(result.(float64), contextTest.Value) + var err error + if !res { + err = fmt.Errorf("does not satisfy the condition %v < %s", result, contextTest.Value) + } + return res, err + case "=": + resultStr := resultByString(result) + res := contextTest.Value == resultStr + var err error + if !res { + err = fmt.Errorf("expected: %s, result: %s", contextTest.Value, resultStr) + } + return res, err + } + return false, nil +} + +func lessInt(intA float64, intB string) bool { + intBVal, _ := strconv.ParseFloat(intB, 64) + if intA < intBVal { + return true + } + return false +} + +func resultByString(result interface{}) string { + if res, ok := result.(string); ok { + return res + } + if res, ok := result.(map[string]interface{}); ok { + newStr := "" + for k, v := range res { + newStr = newStr + k + "=" + fmt.Sprintf("%v", v) + } + return newStr + } + return "" +} diff --git a/src/frontend/user-frontend/.eslintrc.cjs b/src/frontend/user-frontend/.eslintrc.cjs new file mode 100644 index 0000000..ade8571 --- /dev/null +++ b/src/frontend/user-frontend/.eslintrc.cjs @@ -0,0 +1,14 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/src/frontend/user-frontend/Dockerfile b/src/frontend/user-frontend/Dockerfile new file mode 100644 index 0000000..7205a56 --- /dev/null +++ b/src/frontend/user-frontend/Dockerfile @@ -0,0 +1,14 @@ +FROM node:16.20.2 AS build + +COPY . /build +WORKDIR /build + +RUN npm install +RUN npm job build + +FROM nginx:1.26.2 + +COPY --from=build /build/dist /app +COPY nginx.conf /default.conf + +CMD ["/bin/sh", "-c", "DOLLAR=$ envsubst < /default.conf > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"] diff --git a/src/frontend/user-frontend/README.md b/src/frontend/user-frontend/README.md new file mode 100644 index 0000000..ecbe7d3 --- /dev/null +++ b/src/frontend/user-frontend/README.md @@ -0,0 +1,39 @@ +# user-frontend + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm job dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm job build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm job lint +``` diff --git a/src/frontend/user-frontend/components.d.ts b/src/frontend/user-frontend/components.d.ts new file mode 100644 index 0000000..48c269e --- /dev/null +++ b/src/frontend/user-frontend/components.d.ts @@ -0,0 +1,25 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + HomePage: typeof import('./src/components/HomePage.vue')['default'] + IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default'] + IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default'] + IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default'] + IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default'] + IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default'] + Profile: typeof import('./src/components/Profile.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + Runner: typeof import('./src/components/Runner.vue')['default'] + Tasks: typeof import('./src/components/Tasks.vue')['default'] + Themes: typeof import('./src/components/Themes.vue')['default'] + TheWelcome: typeof import('@/components/HomePage.vue')['default'] + WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default'] + } +} diff --git a/src/frontend/user-frontend/env.d.ts b/src/frontend/user-frontend/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/frontend/user-frontend/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/frontend/user-frontend/index.html b/src/frontend/user-frontend/index.html new file mode 100644 index 0000000..0292b82 --- /dev/null +++ b/src/frontend/user-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Education Portal + + +
+ + + diff --git a/src/frontend/user-frontend/jsconfig.json b/src/frontend/user-frontend/jsconfig.json new file mode 100644 index 0000000..4f25534 --- /dev/null +++ b/src/frontend/user-frontend/jsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "es5", + "module": "esnext", + "baseUrl": "./", + "moduleResolution": "node", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/src/frontend/user-frontend/nginx.conf b/src/frontend/user-frontend/nginx.conf new file mode 100644 index 0000000..772170b --- /dev/null +++ b/src/frontend/user-frontend/nginx.conf @@ -0,0 +1,27 @@ +server { + listen 80; + server_name localhost; + client_max_body_size 10M; + + gzip on; + gzip_comp_level 5; + gzip_static on; + + location /api { + proxy_pass ${API_BACKEND}; + proxy_set_header X-Forwarded-Proto ${DOLLAR}scheme; + proxy_set_header X-Forwarded-Port ${DOLLAR}server_port; + proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for; + proxy_set_header Host ${DOLLAR}http_host; + } + + location / { + root /usr/share/nginx/html/; + index index.html index.htm; + try_files ${DOLLAR}uri /index.html; + } + + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/src/frontend/user-frontend/package-lock.json b/src/frontend/user-frontend/package-lock.json new file mode 100644 index 0000000..080c98d --- /dev/null +++ b/src/frontend/user-frontend/package-lock.json @@ -0,0 +1,3651 @@ +{ + "name": "user-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "user-frontend", + "version": "0.0.0", + "dependencies": { + "axios": "^1.7.7", + "chartist": "^1.3.0", + "core-js": "^3.38.1", + "keycloak-js": "^24.0.5", + "vue": "^3.4.29", + "vue-axios": "^3.5.2", + "vue-router": "^4.3.3" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.8.0", + "@tsconfig/node20": "^20.1.4", + "@types/node": "^20.14.5", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.23.0", + "npm-run-all2": "^6.2.0", + "typescript": "~5.4.0", + "unplugin-fonts": "^1.1.1", + "unplugin-vue-components": "^0.27.4", + "unplugin-vue-router": "^0.10.8", + "vite": "^5.3.1", + "vite-plugin-vue-layouts": "^0.11.0", + "vite-plugin-vuetify": "^2.0.4", + "vue-tsc": "^2.0.21" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "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==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "dev": true + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.3.tgz", + "integrity": "sha512-3xbWsKEKXYlmX82aOHufFQVnkbMC/v8fLpWwh6hWOUrK5fbbtBh9Q/WWse27BFgSy2/e2c0fz5Scgya9h2GLhw==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.4.tgz", + "integrity": "sha512-kO9k4kTLfxpg+6lq7/KAIv3m2d62IHuCL6GbVgYZTpfKvIGoAIlDxK7pFcB/eczN2+ydg/vnyaeZ6SGyZrJw2w==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.4" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.4.tgz", + "integrity": "sha512-xG3PZqOP2haG8XG4Pg3PD1UGDAdqZg24Ru8c/qYjYAnmcj6GBR64mstx+bZux5QOyRaJK+/lNM/RnpvBD3489g==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.4.tgz", + "integrity": "sha512-QQMQRVj0fVHJ3XdRKiS1LclhG0VBXdFYlyuHRQF/xLk2PuJuHNWP26MDZNvEVCvnyUQuUQhIAfylwY5TGPgc6w==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.4", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.12.3.tgz", + "integrity": "sha512-dlSqrGdIDhqMOz92XtlMNyuHHeHe594O6f10XLtmlB0Jrq/Pl4Hj8rXAnVlRdjg+ptbZRSNL6MSgOPPoC82owg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.6", + "@rollup/pluginutils": "^5.1.0", + "@vue/compiler-sfc": "^3.5.3", + "ast-kit": "^1.1.0", + "local-pkg": "^0.5.0", + "magic-string-ast": "^0.6.2" + }, + "engines": { + "node": ">=16.14.0" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.3.tgz", + "integrity": "sha512-adAfy9boPkP233NTyvLbGEqVuIfK/R0ZsBsIOW4BZNfb4BRpRW41Do1u+ozJpsb+mdoy80O20IzAsHaihRb5qA==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.3", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.3.tgz", + "integrity": "sha512-wnzFArg9zpvk/811CDOZOadJRugf1Bgl/TQ3RfV4nKfSPok4hi0w10ziYUQR6LnnBAUlEXYLUfZ71Oj9ds/+QA==", + "dependencies": { + "@vue/compiler-core": "3.5.3", + "@vue/shared": "3.5.3" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.3.tgz", + "integrity": "sha512-P3uATLny2tfyvMB04OQFe7Sczteno7SLFxwrOA/dw01pBWQHB5HL15a8PosoNX2aG/EAMGqnXTu+1LnmzFhpTQ==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.3", + "@vue/compiler-dom": "3.5.3", + "@vue/compiler-ssr": "3.5.3", + "@vue/shared": "3.5.3", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.44", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.3.tgz", + "integrity": "sha512-F/5f+r2WzL/2YAPl7UlKcJWHrvoZN8XwEBLnT7S4BXwncH25iDOabhO2M2DWioyTguJAGavDOawejkFXj8EM1w==", + "dependencies": { + "@vue/compiler-dom": "3.5.3", + "@vue/shared": "3.5.3" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz", + "integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "vue-eslint-parser": "^9.3.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "peerDependencies": { + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.0.0", + "typescript": ">=4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.6.tgz", + "integrity": "sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==", + "dev": true, + "dependencies": { + "@volar/language-core": "~2.4.1", + "@vue/compiler-dom": "^3.4.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.4.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.3.tgz", + "integrity": "sha512-2w61UnRWTP7+rj1H/j6FH706gRBHdFVpIqEkSDAyIpafBXYH8xt4gttstbbCWdU3OlcSWO8/3mbKl/93/HSMpw==", + "dependencies": { + "@vue/shared": "3.5.3" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.3.tgz", + "integrity": "sha512-5b2AQw5OZlmCzSsSBWYoZOsy75N4UdMWenTfDdI5bAzXnuVR7iR8Q4AOzQm2OGoA41xjk53VQKrqQhOz2ktWaw==", + "dependencies": { + "@vue/reactivity": "3.5.3", + "@vue/shared": "3.5.3" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.3.tgz", + "integrity": "sha512-wPR1DEGc3XnQ7yHbmkTt3GoY0cEnVGQnARRdAkDzZ8MbUKEs26gogCQo6AOvvgahfjIcnvWJzkZArQ1fmWjcSg==", + "dependencies": { + "@vue/reactivity": "3.5.3", + "@vue/runtime-core": "3.5.3", + "@vue/shared": "3.5.3", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.3.tgz", + "integrity": "sha512-28volmaZVG2PGO3V3+gBPKoSHvLlE8FGfG/GKXKkjjfxLuj/50B/0OQGakM/g6ehQeqCrZYM4eHC4Ks48eig1Q==", + "dependencies": { + "@vue/compiler-ssr": "3.5.3", + "@vue/shared": "3.5.3" + }, + "peerDependencies": { + "vue": "3.5.3" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.3.tgz", + "integrity": "sha512-Jp2v8nylKBT+PlOUjun2Wp/f++TfJVFjshLzNtJDdmFJabJa7noGMncqXRM1vXGX+Yo2V7WykQFNxusSim8SCA==" + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true + }, + "node_modules/@vuetify/loader-shared": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.0.3.tgz", + "integrity": "sha512-Ss3GC7eJYkp2SF6xVzsT7FAruEmdihmn4OCk2+UocREerlXKWgOKKzTN5PN3ZVN5q05jHHrsNhTuWbhN61Bpdg==", + "dev": true, + "dependencies": { + "upath": "^2.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0", + "vuetify": "^3.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-kit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.1.0.tgz", + "integrity": "sha512-RlNqd4u6c/rJ5R+tN/ZTtyNrH8X0NHCvyt6gD8RHa3JjzxxHWoyaU0Ujk3Zjbh7IZqrYl1Sxm6XzZifmVxXxHQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "pathe": "^1.1.2" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.6.2.tgz", + "integrity": "sha512-1UWOyC50xI3QZkRuDj6PqDtpm1oHWtYs+NQGwqL/2R11eN3Q81PHAHPM0SWW3BNQm53UDwS//Jv8L4CCVLM1bQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "ast-kit": "^1.0.1" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "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/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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/chartist": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chartist/-/chartist-1.3.0.tgz", + "integrity": "sha512-M3ckI3ua7EHt08WLZvdi3QXn5g+in27qU6TxjI5bpriS6QwIZlWtisyUhFbpGclW546SlT3SL9oq0vFFDiAo6g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, + "node_modules/core-js": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "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/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/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/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/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.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/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "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-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "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/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "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/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "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==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-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-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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, + "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/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": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/keycloak-js": { + "version": "24.0.5", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-24.0.5.tgz", + "integrity": "sha512-VQOSn3j13DPB6OuavKAq+sRjDERhIKrXgBzekoHRstifPuyULILguugX6yxRUYFSpn3OMYUXmSX++tkdCupOjA==", + "dependencies": { + "js-sha256": "^0.11.0", + "jwt-decode": "^4.0.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/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.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/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magic-string-ast": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.6.2.tgz", + "integrity": "sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.10" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-run-all2": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.2.tgz", + "integrity": "sha512-Q+alQAGIW7ZhKcxLt8GcSi3h3ryheD6xnmXahkMRVM5LYmajcUrSITm8h+OPC9RYWMV2GR0Q1ntTUCfxaNoOJw==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.3", + "memorystream": "^0.3.1", + "minimatch": "^9.0.0", + "pidtree": "^0.6.0", + "read-package-json-fast": "^3.0.2", + "shell-quote": "^1.7.3" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^14.18.0 || ^16.13.0 || >=18.0.0", + "npm": ">= 8" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/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/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pkg-types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", + "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, + "node_modules/postcss": { + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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/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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-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/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/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/unplugin": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.0.tgz", + "integrity": "sha512-cfkZeALGyW7tKYjZbi0G+pn0XnUFa0QvLIeLJEUUlnU0R8YYsBQnt5+h9Eu1B7AB7KETld+UBFI5lOeBL+msoQ==", + "dev": true, + "dependencies": { + "acorn": "^8.12.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/unplugin-fonts": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.1.1.tgz", + "integrity": "sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.12", + "unplugin": "^1.3.1" + }, + "peerDependencies": { + "@nuxt/kit": "^3.0.0", + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz", + "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "chokidar": "^3.6.0", + "debug": "^4.3.6", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.11", + "minimatch": "^9.0.5", + "mlly": "^1.7.1", + "unplugin": "^1.12.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-router": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.10.8.tgz", + "integrity": "sha512-xi+eLweYAqolIoTRSmumbi6Yx0z5M0PLvl+NFNVWHJgmE2ByJG1SZbrn+TqyuDtIyln20KKgq8tqmL7aLoiFjw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.4", + "@rollup/pluginutils": "^5.1.0", + "@vue-macros/common": "^1.12.2", + "ast-walker-scope": "^0.6.2", + "chokidar": "^3.6.0", + "fast-glob": "^3.3.2", + "json5": "^2.2.3", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.11", + "mlly": "^1.7.1", + "pathe": "^1.1.2", + "scule": "^1.3.0", + "unplugin": "^1.12.2", + "yaml": "^2.5.0" + }, + "peerDependencies": { + "vue-router": "^4.4.0" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", + "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-layouts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-layouts/-/vite-plugin-vue-layouts-0.11.0.tgz", + "integrity": "sha512-uh6NW7lt+aOXujK4eHfiNbeo55K9OTuB7fnv+5RVc4OBn/cZull6ThXdYH03JzKanUfgt6QZ37NbbtJ0og59qw==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "fast-glob": "^3.3.2" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.4", + "vue-router": "^4.0.11" + } + }, + "node_modules/vite-plugin-vuetify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.0.4.tgz", + "integrity": "sha512-A4cliYUoP/u4AWSRVRvAPKgpgR987Pss7LpFa7s1GvOe8WjgDq92Rt3eVXrvgxGCWvZsPKziVqfHHdCMqeDhfw==", + "dev": true, + "dependencies": { + "@vuetify/loader-shared": "^2.0.3", + "debug": "^4.3.3", + "upath": "^2.0.1" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": ">=5", + "vue": "^3.0.0", + "vuetify": "^3.0.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, + "node_modules/vue": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.3.tgz", + "integrity": "sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==", + "dependencies": { + "@vue/compiler-dom": "3.5.3", + "@vue/compiler-sfc": "3.5.3", + "@vue/runtime-dom": "3.5.3", + "@vue/server-renderer": "3.5.3", + "@vue/shared": "3.5.3" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-axios": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz", + "integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==", + "peerDependencies": { + "axios": "*", + "vue": "^3.0.0 || ^2.0.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz", + "integrity": "sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==", + "dependencies": { + "@vue/devtools-api": "^6.6.3" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.6.tgz", + "integrity": "sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==", + "dev": true, + "dependencies": { + "@volar/typescript": "~2.4.1", + "@vue/language-core": "2.1.6", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vuetify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.1.tgz", + "integrity": "sha512-N1XlczbgeGt/O+JUk72QPrqcDaRIXUdptUciJqGyTvZ9cfMoSlEWs6TZO+dOOfXbKvmIMFMycYg4dgSHDpCPhg==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20 || >=14.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=1.0.0", + "vue": "^3.3.0", + "webpack-plugin-vuetify": ">=2.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": 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/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": 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/src/frontend/user-frontend/package.json b/src/frontend/user-frontend/package.json new file mode 100644 index 0000000..0aa26ab --- /dev/null +++ b/src/frontend/user-frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "user-frontend", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "axios": "^1.7.7", + "core-js": "^3.38.1", + "keycloak-js": "^24.0.5", + "vue": "^3.4.29", + "vue-axios": "^3.5.2", + "vue-router": "^4.3.3", + "chartist": "^1.3.0" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.8.0", + "@tsconfig/node20": "^20.1.4", + "@types/node": "^20.14.5", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.23.0", + "npm-run-all2": "^6.2.0", + "typescript": "~5.4.0", + "unplugin-fonts": "^1.1.1", + "unplugin-vue-components": "^0.27.4", + "unplugin-vue-router": "^0.10.8", + "vite": "^5.3.1", + "vite-plugin-vue-layouts": "^0.11.0", + "vite-plugin-vuetify": "^2.0.4", + "vue-tsc": "^2.0.21" + } +} diff --git a/src/frontend/user-frontend/src/App.vue b/src/frontend/user-frontend/src/App.vue new file mode 100644 index 0000000..5383139 --- /dev/null +++ b/src/frontend/user-frontend/src/App.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/frontend/user-frontend/src/assets/base.css b/src/frontend/user-frontend/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/src/frontend/user-frontend/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/frontend/user-frontend/src/assets/logo.ico b/src/frontend/user-frontend/src/assets/logo.ico new file mode 100644 index 0000000..80e3cac Binary files /dev/null and b/src/frontend/user-frontend/src/assets/logo.ico differ diff --git a/src/frontend/user-frontend/src/assets/logo.svg b/src/frontend/user-frontend/src/assets/logo.svg new file mode 100644 index 0000000..46e96f0 --- /dev/null +++ b/src/frontend/user-frontend/src/assets/logo.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/frontend/user-frontend/src/assets/main.css b/src/frontend/user-frontend/src/assets/main.css new file mode 100644 index 0000000..6759b0f --- /dev/null +++ b/src/frontend/user-frontend/src/assets/main.css @@ -0,0 +1,30 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/src/frontend/user-frontend/src/assets/project_goals.png b/src/frontend/user-frontend/src/assets/project_goals.png new file mode 100644 index 0000000..8db4930 Binary files /dev/null and b/src/frontend/user-frontend/src/assets/project_goals.png differ diff --git a/src/frontend/user-frontend/src/components/HomePage.vue b/src/frontend/user-frontend/src/components/HomePage.vue new file mode 100644 index 0000000..a1ae2eb --- /dev/null +++ b/src/frontend/user-frontend/src/components/HomePage.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/frontend/user-frontend/src/components/Profile.vue b/src/frontend/user-frontend/src/components/Profile.vue new file mode 100644 index 0000000..45c98b1 --- /dev/null +++ b/src/frontend/user-frontend/src/components/Profile.vue @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/components/Runner.vue b/src/frontend/user-frontend/src/components/Runner.vue new file mode 100644 index 0000000..26a6ce8 --- /dev/null +++ b/src/frontend/user-frontend/src/components/Runner.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/components/Tasks.vue b/src/frontend/user-frontend/src/components/Tasks.vue new file mode 100644 index 0000000..1fcb6aa --- /dev/null +++ b/src/frontend/user-frontend/src/components/Tasks.vue @@ -0,0 +1,32 @@ + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/components/Themes.vue b/src/frontend/user-frontend/src/components/Themes.vue new file mode 100644 index 0000000..40c2bde --- /dev/null +++ b/src/frontend/user-frontend/src/components/Themes.vue @@ -0,0 +1,61 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/components/WelcomeItem.vue b/src/frontend/user-frontend/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/src/frontend/user-frontend/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/frontend/user-frontend/src/components/icons/IconCommunity.vue b/src/frontend/user-frontend/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/src/frontend/user-frontend/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/src/frontend/user-frontend/src/components/icons/IconDocumentation.vue b/src/frontend/user-frontend/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/src/frontend/user-frontend/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/src/frontend/user-frontend/src/components/icons/IconEcosystem.vue b/src/frontend/user-frontend/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/src/frontend/user-frontend/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/src/frontend/user-frontend/src/components/icons/IconSupport.vue b/src/frontend/user-frontend/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/src/frontend/user-frontend/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/src/frontend/user-frontend/src/components/icons/IconTooling.vue b/src/frontend/user-frontend/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/src/frontend/user-frontend/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/src/frontend/user-frontend/src/main.ts b/src/frontend/user-frontend/src/main.ts new file mode 100644 index 0000000..1896651 --- /dev/null +++ b/src/frontend/user-frontend/src/main.ts @@ -0,0 +1,23 @@ +// Plugins +import { registerPlugins } from '@/plugins' + +import './assets/main.css' + +import App from './App.vue' +import { createApp } from 'vue' + +import { login } from '@/plugins/keycloak' +import { setupInterceptors } from '@/plugins/axios' +import {setupRouteWatch} from '@/plugins/router' + + +login(() => { + setupInterceptors() + setupRouteWatch() + + const app = createApp(App) + + registerPlugins(app) + + app.mount('#app') +}) diff --git a/src/frontend/user-frontend/src/plugins/axios.js b/src/frontend/user-frontend/src/plugins/axios.js new file mode 100644 index 0000000..46a8fda --- /dev/null +++ b/src/frontend/user-frontend/src/plugins/axios.js @@ -0,0 +1,22 @@ +import axios from 'axios' +import router from '../router' +import { updateToken } from '@/plugins/keycloak' + +const AUTHORIZATION_HEADER = 'Authorization' + +export function setupInterceptors() { + axios.interceptors.request.use(async config => { + const token = await updateToken() + config.headers[AUTHORIZATION_HEADER] = `Bearer ${token}` + return config + }) + + axios.interceptors.response.use( (response) => { + return response + }, error => { + return new Promise((resolve, reject) => { + router.push('/error') + reject(error) + }) + }) +} diff --git a/src/frontend/user-frontend/src/plugins/index.js b/src/frontend/user-frontend/src/plugins/index.js new file mode 100644 index 0000000..ec90a24 --- /dev/null +++ b/src/frontend/user-frontend/src/plugins/index.js @@ -0,0 +1,9 @@ +import router from '@/router' +import axios from 'axios' +import VueAxios from 'vue-axios' + +export function registerPlugins(app) { + app + .use(router) + .use(VueAxios, axios) +} diff --git a/src/frontend/user-frontend/src/plugins/keycloak.js b/src/frontend/user-frontend/src/plugins/keycloak.js new file mode 100644 index 0000000..a82ae4c --- /dev/null +++ b/src/frontend/user-frontend/src/plugins/keycloak.js @@ -0,0 +1,40 @@ +import Keycloak from 'keycloak-js' + +const initOptions = { + url: import.meta.env.VITE_KEYCLOAK_URL, + realm: 'edu-portal', + clientId: 'api' +} + +const TOKEN_MIN_VALIDITY_SECONDS = 70 + +export const keycloak = new Keycloak(initOptions) + +export async function updateToken() { + await keycloak.updateToken(TOKEN_MIN_VALIDITY_SECONDS) + return keycloak.token +} + +export function login(onAuthenticatedCallback) { + keycloak.init({ onLoad: 'login-required' }).then((auth) => { + if (auth) { + onAuthenticatedCallback() + + window.onfocus = () => { + updateToken() + } + } else { + window.location.reload() + } + }) +} + +export function getRole() { + var currentRole = '' + keycloak.realmAccess['roles'].map(function(role) { + if (['USER', 'CURATOR', 'ADMIN'].includes(role) === true) { + currentRole = role + } + }) + return currentRole +} diff --git a/src/frontend/user-frontend/src/plugins/router.js b/src/frontend/user-frontend/src/plugins/router.js new file mode 100644 index 0000000..f38bdcc --- /dev/null +++ b/src/frontend/user-frontend/src/plugins/router.js @@ -0,0 +1,9 @@ +import { useRoute } from 'vue-router' +import { watch } from 'vue' +import { updateToken } from '@/plugins/keycloak' + +const route = useRoute() + +export function setupRouteWatch() { + watch(route, () => updateToken()) +} diff --git a/src/frontend/user-frontend/src/router/index.ts b/src/frontend/user-frontend/src/router/index.ts new file mode 100644 index 0000000..b7c4011 --- /dev/null +++ b/src/frontend/user-frontend/src/router/index.ts @@ -0,0 +1,45 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import Themes from '@/components/Themes.vue' +import Tasks from '@/components/Tasks.vue' +import Runner from '@/components/Runner.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/themes', + name: 'themes', + component: Themes + }, + { + path: '/theme/:id', + name: 'theme', + component: Tasks, + props: true + }, + { + path: '/task/:id', + name: 'task', + component: Runner, + props: true + }, + { + path: '/about', + name: 'about', + component: () => import('../views/AboutView.vue') + }, + { + path: '/error', + name: 'error', + component: () => import('../views/ErrorView.vue') + } + ] +}) + +export default router diff --git a/src/frontend/user-frontend/src/views/AboutView.vue b/src/frontend/user-frontend/src/views/AboutView.vue new file mode 100644 index 0000000..4999abc --- /dev/null +++ b/src/frontend/user-frontend/src/views/AboutView.vue @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/views/ErrorView.vue b/src/frontend/user-frontend/src/views/ErrorView.vue new file mode 100644 index 0000000..f44f1d1 --- /dev/null +++ b/src/frontend/user-frontend/src/views/ErrorView.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/src/frontend/user-frontend/src/views/HomeView.vue b/src/frontend/user-frontend/src/views/HomeView.vue new file mode 100644 index 0000000..c2efc57 --- /dev/null +++ b/src/frontend/user-frontend/src/views/HomeView.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/frontend/user-frontend/tsconfig.app.json b/src/frontend/user-frontend/tsconfig.app.json new file mode 100644 index 0000000..e14c754 --- /dev/null +++ b/src/frontend/user-frontend/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/src/frontend/user-frontend/tsconfig.json b/src/frontend/user-frontend/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/src/frontend/user-frontend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/src/frontend/user-frontend/tsconfig.node.json b/src/frontend/user-frontend/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/src/frontend/user-frontend/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/src/frontend/user-frontend/typed-router.d.ts b/src/frontend/user-frontend/typed-router.d.ts new file mode 100644 index 0000000..507aa91 --- /dev/null +++ b/src/frontend/user-frontend/typed-router.d.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ +// It's recommended to commit this file. +// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. + +declare module 'vue-router/auto-routes' { + import type { + RouteRecordInfo, + ParamValue, + ParamValueOneOrMore, + ParamValueZeroOrMore, + ParamValueZeroOrOne, + } from 'vue-router' + + /** + * Route name map generated by unplugin-vue-router + */ + export interface RouteNamedMap { + } +} diff --git a/src/frontend/user-frontend/vite.config.mjs b/src/frontend/user-frontend/vite.config.mjs new file mode 100644 index 0000000..f8546a3 --- /dev/null +++ b/src/frontend/user-frontend/vite.config.mjs @@ -0,0 +1,54 @@ +// Plugins +import Components from 'unplugin-vue-components/vite' +import Vue from '@vitejs/plugin-vue' +import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' +import ViteFonts from 'unplugin-fonts/vite' +import Layouts from 'vite-plugin-vue-layouts' +import VueRouter from 'unplugin-vue-router/vite' + +// Utilities +import { defineConfig } from 'vite' +import { fileURLToPath, URL } from 'node:url' +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + VueRouter(), + Layouts(), + Vue({ + template: { transformAssetUrls } + }), + Components() + ], + define: { 'process.env': {} }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + extensions: [ + '.js', + '.json', + '.jsx', + '.mjs', + '.ts', + '.tsx', + '.vue', + ], + }, + server: { + host: 'portal.local', + port: 8081, + cors: false, + proxy: { + '^/api/': { + target: 'http://portal.local:8082', + changeOrigin: true, + secure: false, + }, + '^/keycloak/': { + target: 'http://portal.local:8080', + changeOrigin: true, + secure: false, + } + } + }, +}) diff --git a/src/frontend/user-frontend/vite.config.ts b/src/frontend/user-frontend/vite.config.ts new file mode 100644 index 0000000..5c45e1d --- /dev/null +++ b/src/frontend/user-frontend/vite.config.ts @@ -0,0 +1,16 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +})