diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..107f1a3
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+DB_USER=dfvdsdfvsdswvdfvdsvdfvdf
+DB_PASSWORD=dvnsevhjeiwurowuqvhjevfvuodfb
+DB_NAME=cb
\ No newline at end of file
diff --git a/BACKEND/.dockerignore b/BACKEND/.dockerignore
deleted file mode 100644
index 52bc6f2..0000000
--- a/BACKEND/.dockerignore
+++ /dev/null
@@ -1,35 +0,0 @@
-# Include any files or directories that you don't want to be copied to your
-# container here (e.g., local build artifacts, temporary files, etc.).
-#
-# For more help, visit the .dockerignore file reference guide at
-# https://docs.docker.com/engine/reference/builder/#dockerignore-file
-
-**/.DS_Store
-**/__pycache__
-**/.venv
-**/.classpath
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/bin
-**/charts
-**/docker-compose*
-**/compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
-data
diff --git a/BACKEND/.editorconfig b/BACKEND/.editorconfig
deleted file mode 100644
index d41d83b..0000000
--- a/BACKEND/.editorconfig
+++ /dev/null
@@ -1,27 +0,0 @@
-# EditorConfig is awesome: http://EditorConfig.org
-
-# top-most EditorConfig file
-root = true
-
-# Unix-style newlines with a newline ending every file
-[*]
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-charset = utf-8
-
-# 4 space indentation
-[*.{py,java,r,R}]
-max_line_length = 79
-indent_style = space
-indent_size = 4
-
-# 2 space indentation
-[*.{js,json,yaml,yml,html,cwl}]
-indent_style = space
-indent_size = 2
-
-[*.{md,Rmd,rst}]
-trim_trailing_whitespace = false
-indent_style = space
-indent_size = 2
\ No newline at end of file
diff --git a/BACKEND/.env-example b/BACKEND/.env-example
deleted file mode 100644
index 6fba6ac..0000000
--- a/BACKEND/.env-example
+++ /dev/null
@@ -1,10 +0,0 @@
-DB_HOST=localhost
-DB_USERNAME=postgres
-DB_PASSWORD=admin
-DB_NAME=cb
-CHECKER_PORT=7070
-HASH_SALT=salt
-REDIS_HOST=localhost
-ADMIN_LOGIN=admin
-ADMIN_PASSWORD=admin
-REQUIRE_CAPTCHA=false
diff --git a/BACKEND/.gitignore b/BACKEND/.gitignore
deleted file mode 100644
index b0b6f3a..0000000
--- a/BACKEND/.gitignore
+++ /dev/null
@@ -1,160 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-.idea/
\ No newline at end of file
diff --git a/BACKEND/.pylintrc b/BACKEND/.pylintrc
deleted file mode 100644
index 7af4bb6..0000000
--- a/BACKEND/.pylintrc
+++ /dev/null
@@ -1,2 +0,0 @@
-[MASTER]
-init-hook='import sys; sys.path.append("./src")'
\ No newline at end of file
diff --git a/BACKEND/Dockerfile b/BACKEND/Dockerfile
deleted file mode 100644
index 96397f5..0000000
--- a/BACKEND/Dockerfile
+++ /dev/null
@@ -1,48 +0,0 @@
-# syntax=docker/dockerfile:1
-
-# Comments are provided throughout this file to help you get started.
-# If you need more help, visit the Dockerfile reference guide at
-# https://docs.docker.com/engine/reference/builder/
-
-FROM python:3.9.19-alpine3.20 as base
-
-# Prevents Python from writing pyc files.
-ENV PYTHONDONTWRITEBYTECODE=1
-
-# Keeps Python from buffering stdout and stderr to avoid situations where
-# the application crashes without emitting any logs due to buffering.
-ENV PYTHONUNBUFFERED=1
-
-WORKDIR /app
-
-# Create a non-privileged user that the app will run under.
-# See https://docs.docker.com/go/dockerfile-user-best-practices/
-ARG UID=10001
-RUN adduser \
- --disabled-password \
- --gecos "" \
- --home "/nonexistent" \
- --shell "/sbin/nologin" \
- --no-create-home \
- --uid "${UID}" \
- appuser
-
-# Download dependencies as a separate step to take advantage of Docker's caching.
-# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
-# Leverage a bind mount to requirements.txt to avoid having to copy them into
-# into this layer.
-RUN --mount=type=cache,target=/root/.cache/pip \
- --mount=type=bind,source=requirements.txt,target=requirements.txt \
- python -m pip install -r requirements.txt
-
-# Switch to the non-privileged user to run the application.
-USER appuser
-
-# Copy the source code into the container.
-COPY . .
-
-# Expose the port that the application listens on.
-EXPOSE 8000
-
-# Run the application.
-CMD cd src && gunicorn --workers=4 -b=0.0.0.0:8000 wsgi:app
diff --git a/BACKEND/REDAME.md b/BACKEND/REDAME.md
deleted file mode 100644
index c4d56ba..0000000
--- a/BACKEND/REDAME.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Backend _(aka Application server)_
-
-Used to process web requests
-
-## Files
-
-- Postman API Folder `API.postman_collection.json` at codebattles folder
-
-## Stack
-
-- Python, Flask
-- Gunicorn (wsgi server)
diff --git a/BACKEND/requirements.txt b/BACKEND/requirements.txt
deleted file mode 100644
index cf27f8b..0000000
--- a/BACKEND/requirements.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-requests~=2.31.0
-Flask==3.0.0
-transliterate~=1.10.2
-python-dotenv~=1.0.0
-psycopg2-binary==2.9.9
-Flask-Cors~=4.0.0
-redis~=5.0.1
-captcha~=0.5.0
-gunicorn~=22.0.0
-wtforms~=3.1.2
-wtforms_json~=0.3.5
diff --git a/BACKEND/src/__init__.py b/BACKEND/src/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/app.py b/BACKEND/src/app.py
deleted file mode 100644
index 5ba02db..0000000
--- a/BACKEND/src/app.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import wtforms_json
-from flask import Flask, request, make_response
-
-wtforms_json.init()
-
-app = Flask(__name__)
-
-
-@app.after_request
-def cors_middleware(response):
- origin = request.headers.get("Origin")
- if request.method == "OPTIONS":
- response = make_response()
- response.headers.add("Access-Control-Allow-Credentials", "true")
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
- response.headers.add("Access-Control-Allow-Headers", "x-csrf-token")
- response.headers.add(
- "Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE"
- )
- if origin:
- response.headers.add("Access-Control-Allow-Origin", origin)
- else:
- response.headers.add("Access-Control-Allow-Credentials", "true")
- if origin:
- response.headers.add("Access-Control-Allow-Origin", origin)
-
- return response
diff --git a/BACKEND/src/config.py b/BACKEND/src/config.py
deleted file mode 100644
index 2572468..0000000
--- a/BACKEND/src/config.py
+++ /dev/null
@@ -1,2 +0,0 @@
-ADMIN_LOGIN = "admin"
-ADMIN_PASSWORD = "admin"
diff --git a/BACKEND/src/database/__init__.py b/BACKEND/src/database/__init__.py
deleted file mode 100644
index d7ace94..0000000
--- a/BACKEND/src/database/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import psycopg2
-import env
-
-from database.createTables import (
- CHAMPS_TABLE,
- PROBLEMS_TABLE,
- SERVERS_TABLE,
- STORAGE_TABLE,
- TEACHER_CHAMPS_TABLE,
- GLOBALUSERS_TABLE,
-)
-from database.migrations import sql_migrations
-
-__tables = [
- CHAMPS_TABLE,
- PROBLEMS_TABLE,
- SERVERS_TABLE,
- STORAGE_TABLE,
- TEACHER_CHAMPS_TABLE,
- GLOBALUSERS_TABLE,
-]
-
-
-def get_connection():
- return psycopg2.connect(
- dbname=env.DB_NAME,
- user=env.DB_USERNAME,
- password=env.DB_PASSWORD,
- host=env.DB_HOST,
- )
-
-
-def init_tables():
- connection = get_connection()
- cur = connection.cursor()
-
- for sql in __tables:
- cur.execute(sql)
-
- for sql in sql_migrations:
- cur.execute(sql)
-
- connection.commit()
diff --git a/BACKEND/src/database/createTables.py b/BACKEND/src/database/createTables.py
deleted file mode 100644
index 24a8d65..0000000
--- a/BACKEND/src/database/createTables.py
+++ /dev/null
@@ -1,117 +0,0 @@
-CHAMPS_TABLE = """
- CREATE TABLE IF NOT EXISTS champs (
- id SERIAL PRIMARY KEY,
- name TEXT NOT NULL,
- started TIMESTAMP,
- A INTEGER,
- B INTEGER,
- C INTEGER,
- D INTEGER,
- E INTEGER,
- F INTEGER,
- G INTEGER,
- H INTEGER,
- I INTEGER,
- J INTEGER,
- K INTEGER
- );
-
-"""
-PROBLEMS_TABLE = """
- CREATE TABLE IF NOT EXISTS problems (
- id SERIAL PRIMARY KEY,
- name TEXT,
- description TEXT,
- "in" TEXT,
- "out" TEXT,
- tests TEXT,
- examples TEXT
- );
-"""
-
-SERVERS_TABLE = """
-CREATE TABLE IF NOT EXISTS servers
-(
- id SERIAL PRIMARY KEY,
- name TEXT,
- lang_name TEXT,
- address TEXT,
- enabled boolean DEFAULT true
-)
-"""
-
-STORAGE_TABLE = """
-CREATE TABLE IF NOT EXISTS storage
-(
- id SERIAL PRIMARY KEY,
- key TEXT,
- value TEXT
-)
-"""
-
-
-def get_query_users_table(champ_id):
- return f"""
-CREATE TABLE champUsers_{champ_id} (
- id SERIAL PRIMARY KEY,
- login TEXT,
- password TEXT,
- name TEXT NOT NULL,
- A INTEGER,
- B INTEGER,
- C INTEGER,
- D INTEGER,
- E INTEGER,
- F INTEGER,
- G INTEGER,
- H INTEGER,
- I INTEGER,
- J INTEGER,
- K INTEGER,
- score INTEGER GENERATED ALWAYS AS (COALESCE(A, 0)
- + COALESCE(B, 0) + COALESCE(C, 0) + COALESCE(D, 0)
- + COALESCE(E, 0) + COALESCE(F, 0) + COALESCE(G, 0) + COALESCE(H, 0)
- + COALESCE(I, 0) + COALESCE(J, 0) + COALESCE(K, 0)) STORED
-);
-
- """
-
-
-def get_query_sends_table(champ_id):
- return f"""
- CREATE TABLE champSends_{champ_id} (
- id SERIAL PRIMARY KEY,
- problem_letter TEXT,
- problem_name TEXT NOT NULL,
- problem_id INTEGER,
- user_id INTEGER,
- send_time TIMESTAMP,
- state TEXT,
- description TEXT,
- program TEXT,
- score INTEGER,
- lang TEXT
- );
- """
-
-
-TEACHER_CHAMPS_TABLE = """
-CREATE TABLE IF NOT EXISTS teacher_champs
-(
- id SERIAL PRIMARY KEY,
- teacher_id bigint NOT NULL,
- champ_id bigint NOT NULL,
- "isOwner" boolean NOT NULL DEFAULT true
-)"""
-
-GLOBALUSERS_TABLE = """
-CREATE TABLE IF NOT EXISTS globalusers
-(
- id SERIAL PRIMARY KEY,
- login text NOT NULL,
- password text NOT NULL,
- role text NOT NULL,
- description text,
- name text
-)
-"""
diff --git a/BACKEND/src/database/migrations.py b/BACKEND/src/database/migrations.py
deleted file mode 100644
index eeb73ee..0000000
--- a/BACKEND/src/database/migrations.py
+++ /dev/null
@@ -1,11 +0,0 @@
-_0_ADD_TYPE_OF_PROBLEM = """
-ALTER TABLE problems
-ADD COLUMN IF NOT EXISTS is_question BOOLEAN;
-"""
-
-_1_ADD_TOTP_CODE = """
-ALTER TABLE globalusers
-ADD COLUMN IF NOT EXISTS totp TEXT;
-"""
-
-sql_migrations = [_0_ADD_TYPE_OF_PROBLEM, _1_ADD_TOTP_CODE]
diff --git a/BACKEND/src/database/redis/__init__.py b/BACKEND/src/database/redis/__init__.py
deleted file mode 100644
index 2e3988c..0000000
--- a/BACKEND/src/database/redis/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import redis
-
-redis_pool = None
-
-
-def redis_pool_init():
- global redis_pool
-
- redis_pool = redis.ConnectionPool(
- port=6379,
- decode_responses=True,
- socket_timeout=100,
- )
diff --git a/BACKEND/src/database/redis/redisWrapper.py b/BACKEND/src/database/redis/redisWrapper.py
deleted file mode 100644
index 559201c..0000000
--- a/BACKEND/src/database/redis/redisWrapper.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import redis
-from redis import RedisError
-
-
-class RedisWrapper:
-
- def __init__(self, pool, **kwargs):
- self.__redis_inited = True
- self.__r = redis.Redis(connection_pool=pool, **kwargs)
-
- self.get = self.wrapper(self.__r.get)
- self.set = self.wrapper(self.__r.set)
- self.delete = self.wrapper(self.__r.delete)
-
- def wrapper(self, func):
- def inner(*args, **kwargs):
- if not self.__redis_inited:
- return None
-
- try:
- result = func(*args, **kwargs)
- return result
- except RedisError as e:
- print(e)
- self.__redis_inited = False
- return None
-
- return inner
diff --git a/BACKEND/src/decorators/__init__.py b/BACKEND/src/decorators/__init__.py
deleted file mode 100644
index fdd81dc..0000000
--- a/BACKEND/src/decorators/__init__.py
+++ /dev/null
@@ -1,158 +0,0 @@
-from functools import wraps
-
-import psycopg2
-from flask import request, redirect, make_response
-from psycopg2.extras import RealDictCursor
-
-import env
-from database import get_connection
-from database.redis import redis_pool
-from database.redis.redisWrapper import RedisWrapper
-from utils import salt_crypt
-
-
-def redis_conn(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- r = RedisWrapper(pool=redis_pool, host=env.REDIS_HOST)
- return f(*args, **kwargs, r=r)
-
- return decorated_function
-
-
-def admin_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- admin_login_password = request.cookies.get("admin", None)
- is_admin = admin_login_password == f"{env.ADMIN_LOGIN}_{env.ADMIN_PASSWORD}_531"
-
- if is_admin:
- return f(*args, **kwargs)
- return redirect("/admin/auth")
-
- return decorated_function
-
-
-def teacher_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- teacher_login_password = request.cookies.get("teacher", None)
-
- # print(f"{teacher_login_password=}")
- # is_teacher = teacher_login_password == f"{'login'}_{'psswd'}_88416"
- login, password, client_hash = teacher_login_password.split("_")
-
- predicted_hash = salt_crypt(login, password)
-
- if predicted_hash != client_hash:
- return {"success": False, "msg": "Bad Credentials"}, 403
-
- # return ",", 403
-
- connection = get_connection()
- cursor = connection.cursor(cursor_factory=RealDictCursor)
-
- cursor.execute(
- f"""
- SELECT id from globalusers
- WHERE role = 'TEACHER'
- AND password = %s
- AND login = %s
- """,
- (password, login),
- )
-
- if cursor.fetchone():
- return f(*args, **kwargs)
-
- return {"success": False, "msg": "Bad Credentials"}, 403
-
- return decorated_function
-
-
-def login_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not request.cookies.get("authed", None) is None:
- battle_id = int(request.cookies.get("battle_id"))
- user_id = int(request.cookies.get("user_id"))
- validation_token = request.cookies.get("__validation")
-
- is_valid = salt_crypt(battle_id, user_id) == validation_token
-
- if not is_valid:
- return redirect("/")
-
- connection = get_connection()
- cur = connection.cursor()
-
- try:
- cur.execute(
- f"SELECT * FROM champUsers_{battle_id} " f"WHERE id = {user_id}"
- )
- except psycopg2.errors.UndefinedTable:
- return redirect("/logout")
-
- if cur.fetchone() is None:
- return redirect("/logout")
-
- return f(*args, **kwargs, user_id=battle_id)
-
- return redirect("/")
-
- return decorated_function
-
-
-def reset_cookie_and_return_bad_cred():
- resp = make_response({"success": False, "msg": "Bad Credentials"}, 403)
-
- resp.set_cookie("user_id", expires=0)
- resp.set_cookie("authed", expires=0)
- resp.set_cookie("battle_id", expires=0)
- resp.set_cookie("__validation", expires=0)
-
- return resp
-
-
-def api_login_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not request.cookies.get("authed", None) is None:
- battle_id = int(request.cookies.get("battle_id"))
- user_id = int(request.cookies.get("user_id"))
- validation_token = request.cookies.get("__validation")
-
- is_valid = salt_crypt(battle_id, user_id) == validation_token
-
- if not is_valid:
- return reset_cookie_and_return_bad_cred()
-
- connection = get_connection()
- cur = connection.cursor()
-
- try:
- cur.execute(
- f"SELECT * FROM champUsers_{battle_id}" f" WHERE id = {user_id}"
- )
- except psycopg2.errors.UndefinedTable:
- return reset_cookie_and_return_bad_cred()
-
- if cur.fetchone() is None:
- return reset_cookie_and_return_bad_cred()
- return f(*args, **kwargs, user_id=battle_id)
-
- return reset_cookie_and_return_bad_cred()
-
- return decorated_function
-
-
-def get_user_id(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not request.cookies.get("authed", None) is None:
- user_id = int(request.cookies.get("user_id"))
- return f(*args, **kwargs, uid=user_id)
-
- return redirect("/")
-
- return decorated_function
diff --git a/BACKEND/src/decorators/validation.py b/BACKEND/src/decorators/validation.py
deleted file mode 100644
index 0318958..0000000
--- a/BACKEND/src/decorators/validation.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from functools import wraps
-from typing import Type
-
-from flask import abort, request
-from wtforms import Form
-
-
-def json_validate(clazz: Type[Form]):
- def decorator(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- x: Form = clazz.from_json(request.json)
- if not x.validate():
- return abort(400)
- return f(*args, **kwargs, data=x)
-
- return decorated_function
-
- return decorator
diff --git a/BACKEND/src/env.py b/BACKEND/src/env.py
deleted file mode 100644
index 14950d7..0000000
--- a/BACKEND/src/env.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-
-DB_HOST = None
-DB_USERNAME = None
-DB_PASSWORD = None
-DB_NAME = None
-CHECKER_PORT = None
-HASH_SALT = None
-REDIS_HOST = None
-ADMIN_LOGIN = None
-ADMIN_PASSWORD = None
-REQUIRE_CAPTCHA = None
-
-
-def init():
- global DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME, CHECKER_PORT, HASH_SALT, REDIS_HOST, ADMIN_LOGIN, ADMIN_PASSWORD, REQUIRE_CAPTCHA
-
- DB_HOST = os.environ.get("DB_HOST")
- DB_USERNAME = os.environ.get("DB_USERNAME")
- DB_PASSWORD = os.environ.get("DB_PASSWORD")
- DB_NAME = os.environ.get("DB_NAME")
- CHECKER_PORT = os.environ.get("CHECKER_PORT")
- HASH_SALT = os.environ.get("HASH_SALT")
- REDIS_HOST = os.environ.get("REDIS_HOST")
- ADMIN_LOGIN = os.environ.get("ADMIN_LOGIN")
- ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD")
- REQUIRE_CAPTCHA = os.environ.get("REQUIRE_CAPTCHA")
-
- if REQUIRE_CAPTCHA == "true":
- REQUIRE_CAPTCHA = True
- else:
- REQUIRE_CAPTCHA = False
diff --git a/BACKEND/src/main.py b/BACKEND/src/main.py
deleted file mode 100644
index 55b7bb4..0000000
--- a/BACKEND/src/main.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-from dotenv import load_dotenv
-
-import env
-from app import *
-from database import init_tables
-from database.redis import redis_pool_init
-
-import web
-
-
-def init_env():
- dotenv_path = os.path.join(os.path.dirname(__file__), "../.env")
- if os.path.exists(dotenv_path):
- load_dotenv(dotenv_path)
-
- env.init()
- redis_pool_init()
-
-
-def webapp():
- init_env()
- init_tables()
- return app
-
-
-if __name__ == "__main__":
- webapp().run(host="0.0.0.0", port=2500, debug=True)
diff --git a/BACKEND/src/passwordTools.py b/BACKEND/src/passwordTools.py
deleted file mode 100644
index 77d4b38..0000000
--- a/BACKEND/src/passwordTools.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import random
-import string
-
-
-def get_random_string(length):
- letters = string.digits + string.ascii_letters
- result_str = "".join(random.choice(letters) for _ in range(length))
-
- return result_str
diff --git a/BACKEND/src/services/__init__.py b/BACKEND/src/services/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/services/captcha_service.py b/BACKEND/src/services/captcha_service.py
deleted file mode 100644
index 0f2970a..0000000
--- a/BACKEND/src/services/captcha_service.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import base64
-import random
-from dataclasses import dataclass
-
-from captcha.image import ImageCaptcha
-
-from utils import salt_crypt
-
-numbers_string = "1234567890"
-
-
-@dataclass
-class CaptchaOutput:
- base64image: str
- string: str
-
-
-@dataclass
-class CaptchaValidatedOutput(CaptchaOutput):
- sha256_hash: str
-
-
-def generate(numbers: int = 6) -> CaptchaOutput:
- image = ImageCaptcha()
-
- string_ref = "".join(random.choice(numbers_string) for _ in range(numbers))
- data = image.generate(string_ref)
- encoded_bytes = base64.b64encode(data.getvalue())
- encoded_string = encoded_bytes.decode("utf-8")
-
- return CaptchaOutput(base64image=encoded_string, string=string_ref)
-
-
-def generate_with_validation(numbers: int = 6) -> CaptchaValidatedOutput:
- generated = generate(numbers)
-
- sha_hash = salt_crypt(generated.string + generated.base64image)
-
- return CaptchaValidatedOutput(
- base64image=generated.base64image, string=generated.string, sha256_hash=sha_hash
- )
-
-
-def validate(base64image, string_, original_hash) -> bool:
- user_data_hash = salt_crypt(string_ + base64image)
- print(user_data_hash, original_hash)
- return user_data_hash == original_hash
diff --git a/BACKEND/src/templates/admin/add_users.html b/BACKEND/src/templates/admin/add_users.html
deleted file mode 100644
index ac39930..0000000
--- a/BACKEND/src/templates/admin/add_users.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
Админ панель
-
-
-
Создание пользователей
-
Введите пользователей, каждый на новой строке
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/auth.html b/BACKEND/src/templates/admin/auth.html
deleted file mode 100644
index 7e607b2..0000000
--- a/BACKEND/src/templates/admin/auth.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- Title
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/create.html b/BACKEND/src/templates/admin/create.html
deleted file mode 100644
index 350dfa3..0000000
--- a/BACKEND/src/templates/admin/create.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
Админ панель
-
-
-
-
Создать соревнование
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/panel.html b/BACKEND/src/templates/admin/panel.html
deleted file mode 100644
index 145d950..0000000
--- a/BACKEND/src/templates/admin/panel.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
- {% for champ in champs %}
-
-
-
-
-
ХЗ человек
-
Старт в {{champ[2]}}
-
Конец в ХЗ
-
-
-
- {% endfor %}
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/problems_list.html b/BACKEND/src/templates/admin/problems_list.html
deleted file mode 100644
index 8f22b6d..0000000
--- a/BACKEND/src/templates/admin/problems_list.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
Админ панель
-
-
-
-
-
- Соревнования
-
-
-
-
- {% for problem in problems%}
-
-
-
-
{{problem[0]}}
-
{{problem[1]}}
-
-
-
-
- {% endfor %}
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/settings.html b/BACKEND/src/templates/admin/settings.html
deleted file mode 100644
index 32ad5ce..0000000
--- a/BACKEND/src/templates/admin/settings.html
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
Админ панель
-
-
ID
-
{{id}}
-
{{name}}
-
Тестоовое супер нужное и важное название
-
-
-
- Управление пользователями
-
-
-
-
-
-
- | Буква |
- id задачи |
- Название задачи |
-
-
-
- {% for task in tasks %}
-
- | {{task[0]}} |
- {{task[1]}} |
- {{task[2]}} |
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/templates/admin/users.html b/BACKEND/src/templates/admin/users.html
deleted file mode 100644
index 4b69b70..0000000
--- a/BACKEND/src/templates/admin/users.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
- Document
-
-
-
-
-
-
- Добавить пользователей
-
-
-
-
-
-
- | Имя |
- Логин |
- Пароль |
-
-
-
- {% for task in users %}
-
- | {{task[0]}} |
- {{task[1]}} |
- {{task[2]}} |
-
- {% endfor %}
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BACKEND/src/utils.py b/BACKEND/src/utils.py
deleted file mode 100644
index f032ce0..0000000
--- a/BACKEND/src/utils.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import re
-from hashlib import sha256
-import env
-
-
-def salt_crypt(*args):
- args = list(map(str, args))
- payload = f"{env.HASH_SALT}+++{str(args)}"
- return sha256(payload.encode("utf-8")).hexdigest()
-
-
-def fix_new_line(data):
- print(data)
- if isinstance(data, list):
- out = []
- for i in data:
- out.append(fix_new_line(i))
- return out
- elif isinstance(data, str):
- return data.replace("\\n", "\n")
- else:
- return data
-
-
-def get_table_color_class_by_score(score):
- if score is None:
- return ""
- elif score == 100:
- return "table-success"
-
- elif score == 0:
- return "table-danger"
-
- return "table-warning"
-
-
-def get_table_color_class_by_test_message(msg):
- if msg == "OK" or msg == "SUCCESS":
- return "table-success"
- elif msg == "WRONG_ANSWER":
- return "table-danger"
-
- return "table-info"
-
-
-LETTER_REGEX = re.compile(r"[A-Z]")
diff --git a/BACKEND/src/web/__init__.py b/BACKEND/src/web/__init__.py
deleted file mode 100644
index baa374a..0000000
--- a/BACKEND/src/web/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import web.api
-import web.teacher_api
-import web.admin
-
-import web.solution_processing
-import web.error_handlers
diff --git a/BACKEND/src/web/admin/__init__.py b/BACKEND/src/web/admin/__init__.py
deleted file mode 100644
index 392ff91..0000000
--- a/BACKEND/src/web/admin/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import web.admin.panel
-import web.admin.auth
-import web.admin.problems
diff --git a/BACKEND/src/web/admin/auth.py b/BACKEND/src/web/admin/auth.py
deleted file mode 100644
index 53278d4..0000000
--- a/BACKEND/src/web/admin/auth.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from flask import render_template, request, redirect, make_response
-
-from app import app
-
-
-@app.route("/admin/auth")
-def admin_auth():
- return render_template("admin/auth.html")
-
-
-@app.route("/admin/auth", methods=["POST"])
-def admin_auth_post():
- login, password = request.form["login"], request.form["password"]
-
- resp = make_response(redirect("/admin"))
-
- resp.set_cookie("admin", f"{login}_{password}_531")
-
- print(login, password)
-
- return resp
diff --git a/BACKEND/src/web/admin/panel.py b/BACKEND/src/web/admin/panel.py
deleted file mode 100644
index 275e449..0000000
--- a/BACKEND/src/web/admin/panel.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import random
-import string
-
-from flask import render_template, redirect, request
-
-from app import app
-from database import get_connection
-from database.createTables import get_query_users_table, get_query_sends_table
-from decorators import admin_required
-
-
-@app.route("/admin")
-@admin_required
-def admin_panel():
- connection = get_connection()
- cursor = connection.cursor()
-
- cursor.execute("SELECT id, name, started FROM champs ")
-
- champs = cursor.fetchall()
- champs = list(map(lambda x: [*x], champs))
-
- return render_template("admin/panel.html", champs=champs)
-
-
-@app.route("/admin/champ/")
-@admin_required
-def settings(champ_id):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(champ_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for task in problems_ids_temp:
- if task is not None:
- problems_ids.append(task)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- tasks_dict = dict.fromkeys(problems_ids)
-
- x = list(cur.fetchall())
-
- tasks = []
-
- for i, task in enumerate(x):
- task_id = task[0]
- name = task[1]
- tasks_dict[task_id] = task
- tasks.append((strs[problems_ids.index(task_id)], task_id, name))
-
- tasks.sort()
-
- print()
-
- return render_template(
- "admin/settings.html", tasks=tasks, id=fetch[0], name=fetch[1]
- )
-
-
-@app.route("/admin/champ/", methods=["POST"])
-@admin_required
-def settings_post(champ_id):
- connection = get_connection()
- cur = connection.cursor()
-
- form = request.form
- problem = form["problem"]
- problem_id = form["problem_id"]
-
- if problem_id == "":
- return redirect(f"/admin/champ/{champ_id}")
- problem_id = int(problem_id)
-
- print(problem, problem_id)
-
- cur.execute(f"""UPDATE champs SET {problem} = {problem_id} WHERE id = {champ_id}""")
-
- connection.commit()
-
- return redirect(f"/admin/champ/{champ_id}")
-
-
-@app.route("/admin/create/")
-@admin_required
-def create_champ():
- return render_template("admin/create.html")
-
-
-@app.route("/admin/create/", methods=["POST"])
-@admin_required
-def create_champ_post():
- name = request.form["name"]
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("INSERT INTO champs (name) VALUES (%s)", (name,))
-
- connection.commit()
-
- cur.execute("SELECT currval(pg_get_serial_sequence('champs','id'));")
- champ_id = cur.fetchone()[0]
-
- cur.execute(get_query_users_table(champ_id))
- cur.execute(get_query_sends_table(champ_id))
-
- connection.commit()
-
- return redirect("/admin")
-
-
-@app.route("/admin/champ//add_users")
-@admin_required
-def create_users_in_champ(champ_id):
- return render_template("admin/add_users.html")
-
-
-@app.route("/admin/champ//add_users", methods=["POST"])
-@admin_required
-def create_users_in_champ_post(champ_id):
- users = request.form["users"].split("\r\n")
-
- connection = get_connection()
- cursor = connection.cursor()
-
- for name in users:
- # login = translit(name, 'ru', reversed=True)
- # login = login.replace(" ", "")
- # login = login.replace("'", "")
- # if len(login) > 7:
- # login = login[:6]
- #
- # login = f"{login}{random.randint(10, 99)}"
- #
- # password = get_random_string(8)
-
- login = "".join(map(str, [random.randint(0, 9) for _ in range(5)]))
- password = "".join(map(str, [random.randint(0, 9) for _ in range(5)]))
-
- cursor.execute(
- f"INSERT INTO champUsers_{champ_id} (login, password, name)"
- f" VALUES (%s, %s, %s)",
- (login, password, name),
- )
-
- connection.commit()
-
- return redirect(f"/admin/champ/{champ_id}/users")
-
-
-@app.route("/admin/champ//users")
-@admin_required
-def users_route(champ_id):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute(f"SELECT name, login, password FROM champUsers_{champ_id}")
-
- users = cur.fetchall()
-
- return render_template("admin/users.html", users=users, id=champ_id)
diff --git a/BACKEND/src/web/admin/problems.py b/BACKEND/src/web/admin/problems.py
deleted file mode 100644
index deee7b3..0000000
--- a/BACKEND/src/web/admin/problems.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import json
-
-from flask import render_template, request
-
-from app import app
-from database import get_connection
-from decorators import admin_required
-
-
-@app.route("/admin/problems")
-@admin_required
-def admin_list_problems():
- connection = get_connection()
- cursor = connection.cursor()
-
- cursor.execute("SELECT id, name, description FROM problems")
-
- problems = cursor.fetchall()
-
- problems = list(map(lambda problem: [*problem], problems))
-
- return render_template("admin/problems_list.html", problems=problems)
-
-
-@app.route("/admin/problems/add", methods=["POST"])
-@admin_required
-def admin_list_problems_add():
- connection = get_connection()
- cursor = connection.cursor()
-
- cursor.execute("SELECT id, name, description FROM problems")
-
- problems = cursor.fetchall()
-
- problems = list(map(lambda problem: [*problem], problems))
-
- build = request.form["build"]
- try:
- build_json = json.loads(build)
- build_json["tests"] = json.dumps(build_json["tests"])
- build_json["examples"] = json.dumps(build_json["examples"])
- except Exception as e:
- print("Maybe Json parse exception \n" + str(e))
- return "Not json (404 ERR)", 400
-
- print(build)
- print(build_json)
-
- cursor.execute(
- """INSERT INTO problems (name, description, "in", out, examples, tests) VALUES (%s, %s, %s, %s, %s, %s) """,
- (
- build_json["name"],
- build_json["description"],
- build_json["in"],
- build_json["out"],
- build_json["examples"],
- build_json["tests"],
- ),
- )
-
- connection.commit()
-
- return render_template("admin/problems_list.html", problems=problems)
diff --git a/BACKEND/src/web/api/__init__.py b/BACKEND/src/web/api/__init__.py
deleted file mode 100644
index 6d7d143..0000000
--- a/BACKEND/src/web/api/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import web.api.send_prog
-import web.api.sends
-import web.api.battle
-import web.api.quiz_problems
-import web.api.auth
diff --git a/BACKEND/src/web/api/auth.py b/BACKEND/src/web/api/auth.py
deleted file mode 100644
index 11ad6f9..0000000
--- a/BACKEND/src/web/api/auth.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from flask import request, make_response
-
-from app import app
-from database import get_connection
-from decorators.validation import json_validate
-from utils import salt_crypt
-from web.validation_form.api import LoginForm
-
-
-@app.route("/api/logout", methods=["POST", "GET"])
-def logout_api():
- resp = make_response({"success": True})
-
- resp.set_cookie("user_id", expires=0)
- resp.set_cookie("authed", expires=0)
- resp.set_cookie("battle_id", expires=0)
-
- return resp
-
-
-@app.route("/api/login", methods=["POST"])
-@json_validate(LoginForm)
-def login_post_api(data: LoginForm):
- try:
- champ_id = data.id.data
- login = data.login.data
- password = data.password.data
-
- champ_id = int(champ_id)
-
- con = get_connection()
- cur = con.cursor()
- cur.execute(
- f"SELECT * FROM public.champUsers_{champ_id}"
- f" WHERE login = %s AND password = %s",
- (login, password),
- )
- user = cur.fetchone()
-
- assert user is not None
-
- user_id = str(user[0])
-
- resp = make_response({"success": True})
- resp.set_cookie("user_id", user_id)
- resp.set_cookie("authed", str(True))
- resp.set_cookie("battle_id", str(champ_id))
- resp.set_cookie("__validation", salt_crypt(champ_id, user_id))
-
- return resp
- except Exception as e:
- print(e)
-
- return {"success": False, "msg": "Bad Credentials"}, 403
diff --git a/BACKEND/src/web/api/battle.py b/BACKEND/src/web/api/battle.py
deleted file mode 100644
index 7b66ec1..0000000
--- a/BACKEND/src/web/api/battle.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import json
-import re
-import string
-
-from flask import abort
-
-from app import app
-from database import get_connection
-from decorators import get_user_id, api_login_required, redis_conn
-from utils import fix_new_line, get_table_color_class_by_score, LETTER_REGEX
-
-JSON_MIMETYPE = "application/json"
-
-
-@app.route("/api/problems")
-@api_login_required
-@get_user_id
-def api_problems(user_id, uid):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for task in problems_ids_temp:
- if task is not None:
- problems_ids.append(task)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- tasks_dict = dict.fromkeys(problems_ids)
-
- x = list(cur.fetchall())
-
- tasks = {}
- is_quizes = {}
-
- cur.execute(f"SELECT * FROM champUsers_{user_id} WHERE id = %s", (uid,))
-
- fetch = cur.fetchone() # Can be None
-
- score = fetch[4 : 4 + len(problems_ids)]
-
- css_colors = {}
-
- for i, task in enumerate(x):
- id = task[0]
- name = task[1]
- is_quiz = task[-1]
- if is_quiz is None:
- is_quiz = False
-
- tasks_dict[id] = task
-
- letter = strs[problems_ids.index(id)]
-
- tasks[letter] = name
- is_quizes[letter] = is_quiz
-
- css_colors[letter] = get_table_color_class_by_score(score[strs.index(letter)])
-
- print()
-
- return {
- "success": "true",
- "problems": tasks,
- "colors": css_colors,
- "is_quizes": is_quizes,
- }
-
-
-@app.route("/api/problem/")
-@api_login_required
-def api_problem(letter, user_id):
- if not re.fullmatch(LETTER_REGEX, letter):
- return abort(404)
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for i in problems_ids_temp:
- if i is not None:
- problems_ids.append(i)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- x = list(cur.fetchall())
-
- problem_ = None
-
- for i in x:
- problem_id = i[0]
- problem_letter = strs[problems_ids.index(problem_id)]
- if letter == problem_letter:
- problem_ = i
-
- if problem_ is None:
- abort(404)
-
- cur.execute("SELECT name, id FROM servers WHERE enabled=true")
- servers = cur.fetchall()
-
- _pr_id, _pr_name, _pr_desc, _pr_in, _pr_out, _pr_tests, _pr_examples, _ = problem_
-
- _pr_examples = fix_new_line(json.loads(_pr_examples))
-
- langs = {}
-
- for i in servers:
- langs[i[0]] = i[1]
-
- return dict(
- success=True,
- name=_pr_name,
- description=_pr_desc,
- letter=letter,
- in_data=_pr_in,
- out_data=_pr_out,
- examples=_pr_examples,
- langs=langs,
- )
-
-
-@app.route("/api/stats")
-@api_login_required
-@get_user_id
-@redis_conn
-def api_statistics(user_id, uid, r):
- champ_id = user_id
-
- redis_cache = r.get(f"r-champ-{champ_id}-stats")
- if redis_cache is not None:
- response = app.response_class(
- response=redis_cache, status=200, mimetype=JSON_MIMETYPE
- )
- print("redis")
- return response
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_counts = 0
-
- for i in problems_ids_temp:
- if i is not None:
- problems_counts += 1
- else:
- break
-
- strs = string.ascii_uppercase
-
- print(problems_counts)
-
- cur.execute(
- f"""
- SELECT u.*, MAX(s.send_time) AS send_time
- FROM champusers_{user_id} u
- LEFT JOIN champsends_{user_id} s ON u.id = s.user_id
- GROUP BY u.id
- ORDER BY score DESC, send_time ASC;
- """
- )
-
- fetch = cur.fetchall() # Can be None
-
- users = []
-
- for i, usr in enumerate(fetch):
- user_id = usr[0]
- score = usr[15]
- last_send = usr[16]
- last_send = (
- None if last_send is None else last_send.strftime("%m/%d/%Y, %H:%M:%S")
- )
-
- nickname = usr[3]
- problems_score = usr[4 : problems_counts + 4]
-
- # problems_score = list(map(lambda s: (s, "")[s is None], problems_score))
-
- users.append(
- {
- "position": i + 1,
- "name": nickname,
- "user_id": user_id,
- "score": score,
- "problems_score": problems_score,
- "last_send": last_send,
- }
- )
-
- print()
-
- resp_string = {"success": True, "cols": strs[:problems_counts], "users": users}
-
- r.set(f"r-champ-{champ_id}-stats", json.dumps(resp_string))
- return resp_string
diff --git a/BACKEND/src/web/api/quiz_problems.py b/BACKEND/src/web/api/quiz_problems.py
deleted file mode 100644
index 5d541d2..0000000
--- a/BACKEND/src/web/api/quiz_problems.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import json
-import re
-import string
-
-from flask import abort
-
-from app import app
-from database import get_connection
-from decorators import get_user_id, api_login_required, redis_conn
-from utils import fix_new_line, get_table_color_class_by_score, LETTER_REGEX
-
-JSON_MIMETYPE = "application/json"
-
-
-@app.route("/api/problem//quiz")
-@api_login_required
-def get_quiz(letter, user_id):
- # if not re.fullmatch(LETTER_REGEX, letter):
- # return abort(404)
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for i in problems_ids_temp:
- if i is not None:
- problems_ids.append(i)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- x = list(cur.fetchall())
-
- problem_ = None
-
- for i in x:
- problem_id = i[0]
- problem_letter = strs[problems_ids.index(problem_id)]
- if letter == problem_letter:
- problem_ = i
-
- if problem_ is None:
- abort(404)
-
- _pr_id, _pr_name, _pr_desc, _pr_in, _pr_out, _pr_tests, _pr_examples, _ = problem_
-
- print(_pr_tests)
- _pr_tests = json.loads(_pr_tests)
- filtered_test = []
-
- for question in _pr_tests["questions"]:
- print(question)
- filtered_test.append(
- {
- "id": question["id"],
- "name": question["question"],
- "answers": question["answers"],
- "type": question["type"],
- }
- )
-
- return dict(
- success=True,
- name=_pr_name,
- description=_pr_desc,
- letter=letter,
- tests=filtered_test,
- )
diff --git a/BACKEND/src/web/api/send_prog.py b/BACKEND/src/web/api/send_prog.py
deleted file mode 100644
index c3abc1e..0000000
--- a/BACKEND/src/web/api/send_prog.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import datetime
-import json
-import string
-
-import requests
-
-import env
-from app import app
-from database import get_connection
-from decorators import get_user_id, api_login_required
-from decorators.validation import json_validate
-from web.validation_form.api import SendProgramForm
-
-
-@app.route("/api/send", methods=["POST"])
-@api_login_required
-@get_user_id
-@json_validate(SendProgramForm)
-def api_send_prog(user_id, uid, data: SendProgramForm):
- connection = get_connection()
- cur = connection.cursor()
-
- f_lang = data.cars.data
- f_code = data.src.data
- problem_letter_form = data.problem.data
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for i in problems_ids_temp:
- if i is not None:
- problems_ids.append(i)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- x = list(cur.fetchall())
-
- problem_ = None
-
- for i in x:
- _id = i[0]
- problem_letter = strs[problems_ids.index(_id)]
- if problem_letter_form == problem_letter:
- problem_ = i
-
- tests = problem_[5]
- tests = json.loads(tests)
- tests = list(map(lambda z: {"in": z[0], "out": z[1]}, tests))
-
- print(problem_)
-
- cur.execute(
- f"""
- INSERT INTO champSends_{user_id}
- (problem_name, problem_id, user_id, send_time, state, program, problem_letter, lang)
- VALUES(%s, %s, %s, %s, %s, %s, %s, %s);
- """,
- (
- problem_[1],
- problem_[0],
- uid,
- datetime.datetime.now(),
- "Тестируется",
- f_code,
- problem_letter_form,
- f_lang,
- ),
- )
- cur.execute(f"SELECT currval(pg_get_serial_sequence('champSends_{user_id}','id'));")
-
- inserted_id = cur.fetchone()[0]
-
- meta = {
- "champ_id": user_id,
- "user_id": uid,
- "problem": problem_letter_form,
- "id": inserted_id,
- }
-
- payload = {
- "meta": json.dumps(meta),
- "source": f_code,
- "compiler": f_lang,
- "tests": tests,
- }
-
- connection.commit()
-
- cur.execute(
- f"SELECT address FROM servers WHERE id = %s and enabled = true", (f_lang,)
- )
-
- server_addr = cur.fetchone()
- server_addr = server_addr[0]
- print()
-
- requests.post(f"http://{server_addr}:{env.CHECKER_PORT}/api/v1/test", json=payload)
- return {"success": True}
diff --git a/BACKEND/src/web/api/sends.py b/BACKEND/src/web/api/sends.py
deleted file mode 100644
index 7ad135a..0000000
--- a/BACKEND/src/web/api/sends.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import json
-
-from app import app
-from database import get_connection
-from decorators import get_user_id, api_login_required, redis_conn
-
-
-@app.route("/api/sends")
-@api_login_required
-@get_user_id
-@redis_conn
-def api_sends(user_id, uid, r):
- champ_id = user_id
-
- redis_cache = r.get(f"r-champ-{champ_id}-sends-user-{uid}")
- if redis_cache is not None:
- response = app.response_class(
- response=redis_cache, status=200, mimetype="application/json"
- )
-
- return response
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute(
- f"SELECT * FROM champSends_{user_id} WHERE user_id = %s ORDER BY send_time DESC",
- (uid,),
- )
-
- db_sends = cur.fetchall()
-
- to_render = []
-
- for send in db_sends:
- (
- id,
- letter,
- name,
- problem_id,
- pr_user_id,
- send_time,
- state,
- result,
- program,
- score,
- lang,
- ) = send
-
- human_send_time = send_time.strftime("%m/%d/%Y, %H:%M:%S")
-
- to_render.append(
- dict(
- id=id,
- letter=letter,
- name=name,
- send_time=human_send_time,
- state=state,
- score=(score, "")[score is None],
- program_checked=result is not None,
- )
- )
-
- print()
-
- dict_resp = dict(success=True, sends=to_render)
-
- r.set(f"r-champ-{champ_id}-sends-user-{uid}", json.dumps(dict_resp))
-
- return dict_resp
-
-
-@app.route("/api/send/")
-@api_login_required
-def api_send_viewer(send_id, user_id):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute(
- f"""
- SELECT t1.*, t2.name as lang_name, t2.lang_name as lang_id
- FROM public.champSends_{user_id} as t1
- INNER JOIN public.servers as t2
- on CAST(t1.lang as INTEGER) = t2.id
-
- WHERE t1.id = %s
- """,
- (send_id,),
- )
-
- data = cur.fetchone()
-
- if data is None:
- return {}, 404
-
- result = json.loads(data[7])
-
- prog = data[8]
- lang = data[11]
- lang_id = data[12]
-
- to_render = []
-
- for i, test in enumerate(result):
- message = test["msg"]
- out = test["out"]
-
- if message == "WRONG_ANSWER":
- out = """ВЫВОД СКРЫТ"""
-
- to_add = {"id": i + 1, "time": test["time"], "msg": message, "out": out}
- to_render.append(to_add)
-
- connection.close()
-
- print()
-
- return {
- "success": True,
- "tests": to_render,
- "lang": lang,
- "program": prog,
- "lang_id": lang_id,
- }
diff --git a/BACKEND/src/web/error_handlers.py b/BACKEND/src/web/error_handlers.py
deleted file mode 100644
index b3507fe..0000000
--- a/BACKEND/src/web/error_handlers.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from app import app
-
-
-@app.errorhandler(404)
-@app.errorhandler(403)
-@app.errorhandler(405)
-@app.errorhandler(500)
-def resource_not_found(e):
- return {"success": False, "status": e.code, "error": str(e)}, e.code
diff --git a/BACKEND/src/web/solution_processing/__init__.py b/BACKEND/src/web/solution_processing/__init__.py
deleted file mode 100644
index 881f412..0000000
--- a/BACKEND/src/web/solution_processing/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-import web.solution_processing.checker_api
-import web.solution_processing.quiz_api
diff --git a/BACKEND/src/web/solution_processing/checker_api.py b/BACKEND/src/web/solution_processing/checker_api.py
deleted file mode 100644
index e21ab11..0000000
--- a/BACKEND/src/web/solution_processing/checker_api.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import json
-import re
-import string
-import datetime
-
-import requests
-from flask import redirect, request
-
-from app import app
-from database import get_connection
-from decorators import login_required, get_user_id, redis_conn
-import env
-
-
-@app.route("/send", methods=["POST"])
-@login_required
-@get_user_id
-def send_prog(user_id, uid):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for i in problems_ids_temp:
- if i is not None:
- problems_ids.append(i)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- x = list(cur.fetchall())
-
- problem_ = None
- problem_letter_form = request.form["problem"]
-
- for i in x:
- _id = i[0]
- problem_letter = strs[problems_ids.index(_id)]
- if problem_letter_form == problem_letter:
- problem_ = i
-
- tests = problem_[5]
- tests = json.loads(tests)
- tests = list(map(lambda z: {"in": z[0], "out": z[1]}, tests))
-
- print(problem_)
-
- f_lang = request.form["cars"]
- f_code = request.form["src"]
-
- cur.execute(
- f"""
- INSERT INTO champSends_{user_id}
- (problem_name, problem_id, user_id, send_time, state, program, problem_letter, lang)
- VALUES(%s, %s, %s, %s, %s, %s, %s, %s);
- """,
- (
- problem_[1],
- problem_[0],
- uid,
- datetime.datetime.now(),
- "Тестируется",
- f_code,
- problem_letter_form,
- f_lang,
- ),
- )
- cur.execute(f"SELECT currval(pg_get_serial_sequence('champSends_{user_id}','id'));")
-
- inserted_id = cur.fetchone()[0]
-
- meta = {
- "champ_id": user_id,
- "user_id": uid,
- "problem": request.form["problem"],
- "id": inserted_id,
- }
-
- data = {
- "meta": json.dumps(meta),
- "source": request.form["src"],
- "compiler": request.form["cars"],
- "tests": tests,
- }
-
- connection.commit()
-
- print(cur.fetchone())
-
- cur.execute(f"SELECT address FROM servers WHERE id = %s", (request.form["cars"],))
-
- server_addr = cur.fetchone()[0]
-
- print()
-
- requests.post(
- f"http://{server_addr}:{env.CHECKER_PORT}/api/v1/test", json=data, timeout=1000
- )
- return redirect("/sends")
-
-
-@app.route("/api/check_system_callback", methods=["POST"])
-@redis_conn
-def check_system(r):
- data = request.json
- print(data)
- all_count = 0
- correct_count = 0
- for results in data["results"]:
- all_count += 1
- if results["success"]:
- correct_count += 1
-
- meta = json.loads(data["meta"])
-
- print(round((correct_count / all_count) * 100))
-
- champ_id = meta["champ_id"]
- user_id = meta["user_id"]
- column = meta["problem"][0]
-
- if not re.fullmatch("[0-9]+", str(champ_id)):
- return "", 409
- if not re.fullmatch("[0-9]+", str(user_id)):
- return "", 409
- if not re.fullmatch("[a-zA-Z]", str(column)):
- return "", 409
-
- con = get_connection()
- cur = con.cursor()
-
- points = round((correct_count / all_count) * 100)
-
- cur.execute(
- f"UPDATE champUsers_{champ_id} SET {column} = {points} \
- WHERE id= {user_id} AND ({column} < {points} OR {column} IS NULL)"
- )
-
- result_str = json.dumps(data["results"], indent=2)
-
- print(result_str)
-
- cur.execute(
- f"UPDATE champSends_{champ_id} SET score = %s,state = 'Протестировано', description = %s WHERE id = %s;",
- (round((correct_count / all_count) * 100), result_str, meta["id"]),
- )
-
- con.commit()
-
- r.delete(f"r-champ-{champ_id}-stats", f"r-champ-{champ_id}-sends-user-{user_id}")
-
- return "OK"
diff --git a/BACKEND/src/web/solution_processing/quiz_api.py b/BACKEND/src/web/solution_processing/quiz_api.py
deleted file mode 100644
index 8e53534..0000000
--- a/BACKEND/src/web/solution_processing/quiz_api.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import datetime
-import json
-import re
-
-from flask import request
-from psycopg2.extras import RealDictCursor
-
-from app import app
-from database import get_connection
-from decorators import login_required, get_user_id, redis_conn
-
-
-@app.route("/api/send/quiz", methods=["POST"])
-@login_required
-@get_user_id
-@redis_conn
-def send_quiz_solution(user_id, uid, r):
- connection = get_connection()
- cur = connection.cursor()
- cur_dict = connection.cursor(cursor_factory=RealDictCursor)
-
- answers: dict
- problem, answers = request.json["problem"], request.json["answers"]
-
- if not re.fullmatch("[a-zA-Z]", problem):
- return "", 409
-
- cur.execute(f"SELECT {problem.lower()} FROM champs WHERE id = %s", (str(user_id),))
-
- fetch = cur.fetchone() # Can be None
- problem_id = fetch[0]
- print(problem_id)
-
- cur_dict.execute(f"SELECT tests, name FROM problems WHERE id = %s", (problem_id,))
- fetch = cur_dict.fetchone()
- tests = fetch["tests"]
- problem_name = fetch["name"]
-
- tests_dict = json.loads(tests)
- questionById = {}
- qustionsCount = 0
- for question in tests_dict["questions"]:
- questionById[question["id"]] = question
- qustionsCount += 1
- print(questionById)
-
- points = 0
- report = ""
- for answer_id, answer in answers.items():
- current_answer = answer[0]
- print(questionById)
- correct_answer = questionById[int(answer_id)]["correct_answers"][0]
- print(correct_answer)
- report += "=" * 30 + "\n"
- report += "Expected: " + "\n"
- report += str(correct_answer) + "\n"
- report += "Answered: " + "\n"
- report += str(current_answer) + "\n"
-
- if current_answer == correct_answer:
- points += 1
- report += f"Got 1 point" + "\n"
- else:
- report += f"Got 0 point" + "\n"
-
- report += "=" * 30 + "\n"
-
- totalPoints = int(points / qustionsCount * 100)
-
- report += "\n"
- report += f"Final points: {points}/{qustionsCount} => {totalPoints}"
- report += "\n"
-
- cur.execute(
- f"""SELECT id FROM champSends_{user_id} where user_id={uid} and problem_id={problem_id}"""
- )
- equals_sends = cur.fetchall()
- print("!!!!!!", equals_sends)
-
- if len(equals_sends) == 0:
-
- cur.execute(
- f"""
- INSERT INTO champSends_{user_id}
- (problem_name, problem_id, user_id, send_time, state, program, problem_letter, score)
- VALUES(%s, %s, %s, %s, %s, %s, %s, %s);
- """,
- (
- problem_name,
- problem_id,
- uid,
- datetime.datetime.now(),
- "Подсчитано",
- report,
- problem,
- totalPoints,
- ),
- )
-
- cur.execute(
- f"""
- UPDATE champUsers_{user_id} SET {problem.lower()} = {totalPoints}
- WHERE id= {uid}"""
- )
-
- connection.commit()
-
- r.delete(f"r-champ-{user_id}-stats", f"r-champ-{user_id}-sends-user-{uid}")
-
- return {"ok": "ok"}
diff --git a/BACKEND/src/web/teacher_api/__init__.py b/BACKEND/src/web/teacher_api/__init__.py
deleted file mode 100644
index c6638cb..0000000
--- a/BACKEND/src/web/teacher_api/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import web.teacher_api.auth
-import web.teacher_api.champs
-import web.teacher_api.students
-import web.teacher_api.problems
-import web.teacher_api.students_sends
diff --git a/BACKEND/src/web/teacher_api/auth.py b/BACKEND/src/web/teacher_api/auth.py
deleted file mode 100644
index 6d21b32..0000000
--- a/BACKEND/src/web/teacher_api/auth.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from flask import request, make_response, jsonify
-from psycopg2.extras import RealDictCursor
-
-import env
-from app import app
-from database import get_connection
-from decorators import teacher_required
-from services import captcha_service
-from utils import salt_crypt
-
-
-@app.route("/api/teacher/auth")
-def teacher_auth_captcha():
- captcha = captcha_service.generate_with_validation()
- return {
- "base64string": captcha.base64image,
- "validate": captcha.sha256_hash,
- }
-
-
-@app.route("/api/teacher/auth", methods=["POST"])
-def teacher_auth_post():
- login, password = request.json["login"], request.json["password"]
-
- if env.REQUIRE_CAPTCHA:
- base64image, captchaUserInput, captchaValidate = (
- request.json["base64image"],
- request.json["captchaUserInput"],
- request.json["captchaValidate"],
- )
-
- success_captcha = captcha_service.validate(
- base64image, captchaUserInput, captchaValidate
- )
-
- if not success_captcha:
- return make_response({"success": False, "use_redirect": False}, 403)
-
- connection = get_connection()
- cursor = connection.cursor(cursor_factory=RealDictCursor)
-
- cursor.execute(
- f"""
- SELECT id from globalusers
- WHERE role = 'TEACHER'
- AND password = %s
- AND login = %s
- """,
- (password, login),
- )
-
- resp = make_response({"success": False, "use_redirect": False}, 403)
-
- if cursor.fetchone():
- resp = make_response({"success": True})
- client_hash = salt_crypt(login, password)
- resp.set_cookie("teacher", f"{login}_{password}_{client_hash}")
-
- return resp
-
-
-@app.route("/api/teacher/changecred", methods=["POST"])
-@teacher_required
-def teacher_change_credential_post():
- new_login, new_password, current_password = (
- request.json["login"],
- request.json["password"],
- request.json["current_password"],
- )
-
- connection = get_connection()
- cursor = connection.cursor(cursor_factory=RealDictCursor)
-
- cursor.execute(
- f"""
- SELECT id from globalusers
- WHERE role = 'TEACHER'
- AND password = %s
- AND login = %s
- """,
- (current_password, new_login),
- )
-
- resp = make_response({"success": False, "use_redirect": False}, 422)
-
- user_db = cursor.fetchone()
-
- print(user_db)
- if user_db is None:
- pass
- elif len(user_db) > 0:
- cursor.execute(
- "UPDATE globalusers SET login = %s, password = %s WHERE id = %s;",
- (new_login, new_password, user_db["id"]),
- )
- resp = make_response({"success": True})
- client_hash = salt_crypt(new_login, new_password)
- resp.set_cookie("teacher", f"{new_login}_{new_password}_{client_hash}")
-
- connection.commit()
-
- return resp
diff --git a/BACKEND/src/web/teacher_api/champs.py b/BACKEND/src/web/teacher_api/champs.py
deleted file mode 100644
index 9795ed8..0000000
--- a/BACKEND/src/web/teacher_api/champs.py
+++ /dev/null
@@ -1,121 +0,0 @@
-import re
-import string
-
-from flask import request, redirect
-
-from app import app
-from database import get_connection
-from database.createTables import get_query_users_table, get_query_sends_table
-from decorators import teacher_required
-
-
-@app.route("/api/teacher/champs")
-@teacher_required
-def get_champs_route():
- connection = get_connection()
- cursor = connection.cursor()
-
- cursor.execute("SELECT id, name, started FROM champs ")
-
- champs = cursor.fetchall()
- champs = list(map(lambda x: [*x], champs))
- champs = list(map(lambda x: {"id": x[0], "name": x[1], "start_dt": x[2]}, champs))
-
- return champs
-
-
-@app.route("/api/teacher/champs", methods=["POST"])
-@teacher_required
-def create_champ_post_teacher():
- name = request.json["name"]
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("INSERT INTO champs (name) VALUES (%s)", (name,))
-
- connection.commit()
-
- cur.execute("SELECT currval(pg_get_serial_sequence('champs','id'));")
- champ_id = cur.fetchone()[0]
-
- cur.execute(get_query_users_table(champ_id))
- cur.execute(get_query_sends_table(champ_id))
-
- connection.commit()
-
- return redirect("/admin")
-
-
-@app.route("/api/teacher/champs/")
-@teacher_required
-# @ValidateParameters
-def get_champs_byid_route(champ_id):
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(champ_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_ids = []
-
- sql = "SELECT * FROM problems WHERE id = -1 "
-
- strs = string.ascii_uppercase
-
- for task in problems_ids_temp:
- if task is not None:
- problems_ids.append(task)
- sql += "OR id = %s "
-
- cur.execute(sql, tuple(problems_ids))
-
- tasks_dict = dict.fromkeys(problems_ids)
-
- x = list(cur.fetchall())
-
- tasks = []
-
- for task in x:
- _id = task[0]
- name = task[1]
- tasks_dict[_id] = task
- tasks.append({"letter": strs[problems_ids.index(_id)], "id": _id, "name": name})
-
- return {"tasks": tasks, "id": fetch[0], "name": fetch[1]}
-
-
-@app.route("/api/teacher/champs/", methods=["POST"])
-@teacher_required
-def settings_post_teacher_api(champ_id):
- connection = get_connection()
- cur = connection.cursor()
-
- form = request.json
- problem = form["problem"]
- problem_id = form["problem_id"]
-
- cur.execute(f"""SELECT id FROM problems WHERE id=%s""", (problem_id,))
-
- prefetched_problem = cur.fetchone()
-
- print(prefetched_problem)
-
- if problem_id == "" or prefetched_problem is None:
- return {"success": "false"}, 400
- problem_id = int(problem_id)
-
- print(problem, problem_id)
-
- problem_validation_result = re.fullmatch("[a-zA-z]", problem)
- if not problem_validation_result:
- return {"success": "false"}, 400
-
- cur.execute(
- f"""UPDATE champs SET {problem} = %s WHERE id = %s""", (problem_id, champ_id)
- )
-
- connection.commit()
-
- return {"success": "true"}
diff --git a/BACKEND/src/web/teacher_api/problems.py b/BACKEND/src/web/teacher_api/problems.py
deleted file mode 100644
index d253d92..0000000
--- a/BACKEND/src/web/teacher_api/problems.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import json
-
-from flask import request
-from psycopg2.extras import RealDictCursor
-
-from app import app
-from database import get_connection
-from decorators import teacher_required
-from utils import fix_new_line
-
-
-@app.route("/api/teacher/problems")
-@teacher_required
-def get_problems_api_route():
- connection = get_connection()
- cursor = connection.cursor(cursor_factory=RealDictCursor)
-
- cursor.execute("SELECT id, name, description FROM problems")
- problems = cursor.fetchall()
-
- return problems
-
-
-@app.route("/api/teacher/problems/")
-@teacher_required
-def get_problems_byid_api_route(problem_id):
- connection = get_connection()
- cursor = connection.cursor(cursor_factory=RealDictCursor)
-
- cursor.execute(
- f"""
- SELECT id, name, description, "in", "out", examples, tests
- FROM problems WHERE id = %s
- """,
- (problem_id,),
- )
- problem = cursor.fetchone()
-
- print(problem)
-
- if problem is None:
- return "", 404
-
- out_data = {
- "tests": [],
- "examples": fix_new_line(json.loads(problem["examples"])),
- "name": problem["name"],
- "description": problem["description"],
- "out_data": problem["out"],
- "in_data": problem["in"],
- }
-
- return out_data
-
-
-@app.route("/api/teacher/problems/add", methods=["POST"])
-@teacher_required
-def teacher_list_problems_add():
- connection = get_connection()
- cursor = connection.cursor()
-
- build = request.json["build"]
- print(build)
-
- try:
- build_json = json.loads(build)
-
- problem_type = build_json.get("type", "question")
- except Exception as e:
- print("Maybe Json parse exception \n" + str(e))
- return "Not json (404 ERR)", 400
-
- print()
-
- if problem_type == "quiz":
- print()
-
- cursor.execute(
- """
- INSERT INTO problems (name, description, "in", out, examples, tests, is_question)
- VALUES (%s, %s, %s, %s, %s, %s, TRUE)
- """,
- (build_json["name"], "Тест", "-", "-", "[]", json.dumps(build_json)),
- )
-
- pass
- elif problem_type == "question":
- build_json["tests"] = json.dumps(build_json["tests"])
- build_json["examples"] = json.dumps(build_json["examples"])
-
- print(build)
- print(build_json)
-
- cursor.execute(
- """
- INSERT INTO problems (name, description, "in", out, examples, tests)
- VALUES (%s, %s, %s, %s, %s, %s)
- """,
- (
- build_json["name"],
- build_json["description"],
- build_json["in"],
- build_json["out"],
- build_json["examples"],
- build_json["tests"],
- ),
- )
-
- connection.commit()
-
- return {"success": True}
diff --git a/BACKEND/src/web/teacher_api/students.py b/BACKEND/src/web/teacher_api/students.py
deleted file mode 100644
index 752e977..0000000
--- a/BACKEND/src/web/teacher_api/students.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import random
-
-from flask import request
-from psycopg2.extras import RealDictCursor
-
-from app import app
-from database import get_connection
-from decorators import teacher_required
-
-from decorators import redis_conn
-
-
-@app.route("/api/teacher/champs//users")
-@teacher_required
-def get_users_get_route(champ_id):
- connection = get_connection()
- cur = connection.cursor(cursor_factory=RealDictCursor)
-
- cur.execute(f"SELECT name, login, password FROM champUsers_{champ_id}")
- users = cur.fetchall()
-
- return users
-
-
-@app.route("/api/teacher/champs//add_users", methods=["POST"])
-@teacher_required
-@redis_conn
-def create_users_in_champ_post_teachers_api(champ_id, r):
- users = request.json["users"].replace("\r", "").split("\n")
-
- connection = get_connection()
- cursor = connection.cursor()
-
- for name in users:
- login = "".join(map(str, [random.randint(0, 9) for _ in range(5)]))
- password = "".join(map(str, [random.randint(0, 9) for _ in range(5)]))
-
- cursor.execute(
- f"INSERT INTO champUsers_{champ_id} (login, password, name) VALUES (%s, %s, %s)",
- (login, password, name),
- )
-
- connection.commit()
-
- r.delete(f"r-champ-{champ_id}-stats")
-
- return {"success": "true"}, 201
diff --git a/BACKEND/src/web/teacher_api/students_sends.py b/BACKEND/src/web/teacher_api/students_sends.py
deleted file mode 100644
index a167c0e..0000000
--- a/BACKEND/src/web/teacher_api/students_sends.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import json
-import string
-
-import psycopg2
-from flask import request, abort
-from psycopg2.extras import RealDictCursor
-
-from app import app
-from database import get_connection
-from decorators import redis_conn
-from web.api.battle import JSON_MIMETYPE
-
-
-@app.route("/api/teacher/champs//stats")
-@redis_conn
-def get_stats_teacher_api(champ_id, r):
- redis_cache = r.get(f"r-champ-{champ_id}-stats")
- if redis_cache is not None:
- response = app.response_class(
- response=redis_cache,
- status=200,
- mimetype=JSON_MIMETYPE,
- )
- print("redis")
- return response
-
- connection = get_connection()
- cur = connection.cursor()
-
- cur.execute("SELECT * FROM champs WHERE id = %s", (str(champ_id),))
-
- fetch = cur.fetchone() # Can be None
- problems_ids_temp = fetch[3:]
- problems_counts = 0
-
- for i in problems_ids_temp:
- if i is not None:
- problems_counts += 1
- else:
- break
-
- strs = string.ascii_uppercase
-
- print(problems_counts)
-
- cur.execute(
- f"""
- SELECT u.*, MAX(s.send_time) AS send_time
- FROM champusers_{champ_id} u
- LEFT JOIN champsends_{champ_id} s ON u.id = s.user_id
- GROUP BY u.id
- ORDER BY score DESC, send_time ASC;
- """
- )
-
- fetch = cur.fetchall() # Can be None
-
- users = []
-
- for i, usr in enumerate(fetch):
- user_id = usr[0]
- score = usr[15]
- last_send = usr[16]
- last_send = (
- None if last_send is None else last_send.strftime("%m/%d/%Y, %H:%M:%S")
- )
-
- nickname = usr[3]
- problems_score = usr[4 : problems_counts + 4]
-
- # problems_score = list(map(lambda s: (s, "")[s is None], problems_score))
-
- users.append(
- {
- "position": i + 1,
- "name": nickname,
- "user_id": user_id,
- "score": score,
- "problems_score": problems_score,
- "last_send": last_send,
- }
- )
-
- print()
-
- resp_string = {"success": True, "cols": strs[:problems_counts], "users": users}
-
- r.set(f"r-champ-{champ_id}-stats", json.dumps(resp_string))
- return resp_string
-
-
-@app.route("/api/teacher/champs//stats/search")
-def get_stats_teacher_api_get_by_task_and_user(champ_id):
- args = request.args
- user_id, problem_letter = args["user_id"], args["problem"]
-
- conn = get_connection()
- cursor = conn.cursor(cursor_factory=RealDictCursor)
-
- try:
- cursor.execute(
- f"""
- SELECT *
- FROM champsends_{champ_id}
- WHERE problem_letter = %s
- AND user_id = %s
- ORDER BY score DESC, send_time DESC
- LIMIT 1
-
- """,
- (problem_letter, user_id),
- )
- except psycopg2.errors.UndefinedTable:
- abort(404)
-
- res = cursor.fetchone()
-
- if res is None:
- abort(404)
-
- tests = []
- result = []
- try:
- result = json.loads(res["description"])
- except TypeError:
- pass
-
- for i, test in enumerate(result):
- message = test["msg"]
- out = test["out"]
-
- if message == "WRONG_ANSWER":
- out = """ВЫВОД СКРЫТ"""
-
- to_add = {"id": i + 1, "time": test["time"], "msg": message, "out": out}
- tests.append(to_add)
-
- return {**{"tests": tests}, **res}
diff --git a/BACKEND/src/web/validation_form/__init__.py b/BACKEND/src/web/validation_form/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/web/validation_form/admin.py b/BACKEND/src/web/validation_form/admin.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/web/validation_form/api.py b/BACKEND/src/web/validation_form/api.py
deleted file mode 100644
index 24f5ffb..0000000
--- a/BACKEND/src/web/validation_form/api.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from wtforms import Form, StringField, IntegerField
-from wtforms.validators import DataRequired, Regexp
-
-from utils import LETTER_REGEX
-
-
-class LoginForm(Form):
- id = IntegerField("id", validators=[DataRequired()])
- login = StringField("login", validators=[DataRequired()])
- password = StringField("password", validators=[DataRequired()])
-
-
-class SendProgramForm(Form):
- cars = StringField("cars", validators=[DataRequired()])
- src = StringField("src", validators=[DataRequired()])
- problem = StringField("problem", validators=[DataRequired(), Regexp(LETTER_REGEX)])
diff --git a/BACKEND/src/web/validation_form/solution_processing.py b/BACKEND/src/web/validation_form/solution_processing.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/web/validation_form/teacher_api.py b/BACKEND/src/web/validation_form/teacher_api.py
deleted file mode 100644
index e69de29..0000000
diff --git a/BACKEND/src/wsgi.py b/BACKEND/src/wsgi.py
deleted file mode 100644
index 033109f..0000000
--- a/BACKEND/src/wsgi.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from main import webapp
-
-app = webapp()
diff --git a/BACKEND_V2/.gitattributes b/BACKEND_V2/.gitattributes
new file mode 100644
index 0000000..8af972c
--- /dev/null
+++ b/BACKEND_V2/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/BACKEND_V2/.gitignore b/BACKEND_V2/.gitignore
new file mode 100644
index 0000000..5a979af
--- /dev/null
+++ b/BACKEND_V2/.gitignore
@@ -0,0 +1,40 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Kotlin ###
+.kotlin
diff --git a/BACKEND_V2/Dockerfile b/BACKEND_V2/Dockerfile
new file mode 100644
index 0000000..5231258
--- /dev/null
+++ b/BACKEND_V2/Dockerfile
@@ -0,0 +1,10 @@
+FROM openjdk:17-jdk-slim AS build
+WORKDIR /app
+COPY . .
+RUN ./gradlew build -x test
+
+FROM openjdk:17-jdk-slim
+WORKDIR /app
+COPY --from=build /app/build/libs/*.jar app.jar
+EXPOSE 8080
+CMD ["java", "-jar", "app.jar"]
diff --git a/BACKEND_V2/build.gradle.kts b/BACKEND_V2/build.gradle.kts
new file mode 100644
index 0000000..d1d0280
--- /dev/null
+++ b/BACKEND_V2/build.gradle.kts
@@ -0,0 +1,81 @@
+plugins {
+ kotlin("jvm") version "1.9.25"
+ kotlin("plugin.spring") version "1.9.25"
+ id("org.springframework.boot") version "3.4.1"
+ id("io.spring.dependency-management") version "1.1.7"
+ kotlin("plugin.jpa") version "1.9.25"
+ id("org.jetbrains.kotlin.kapt") version "1.9.25"
+}
+
+group = "ru.codebattles"
+version = "0.0.1-SNAPSHOT"
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+
+ implementation("org.springframework.boot:spring-boot-starter-data-jpa")
+ implementation("org.springframework.boot:spring-boot-starter-web")
+ implementation("org.springframework.boot:spring-boot-starter-security")
+ implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+ implementation("org.jetbrains.kotlin:kotlin-reflect")
+ implementation("org.springdoc:springdoc-openapi-starter-common:2.8.8")
+ implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8")
+ implementation("org.flywaydb:flyway-core:11.8.2")
+ testImplementation("org.springframework.boot:spring-boot-starter-test")
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+ compileOnly("org.projectlombok:lombok")
+ runtimeOnly("org.postgresql:postgresql")
+ // https://mvnrepository.com/artifact/org.flywaydb/flyway-database-postgresql
+ runtimeOnly("org.flywaydb:flyway-database-postgresql:11.8.1")
+ annotationProcessor("org.projectlombok:lombok")
+
+ implementation("org.springframework.boot:spring-boot-starter-actuator")
+
+ implementation("org.mapstruct:mapstruct:1.5.5.Final")
+ kapt("org.mapstruct:mapstruct-processor:1.5.5.Final")
+
+ implementation("io.jsonwebtoken:jjwt-api:0.11.5")
+ implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
+ implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
+}
+
+kotlin {
+ compilerOptions {
+ freeCompilerArgs.addAll("-Xjsr305=strict")
+ }
+}
+
+allOpen {
+ annotation("jakarta.persistence.Entity")
+ annotation("jakarta.persistence.MappedSuperclass")
+ annotation("jakarta.persistence.Embeddable")
+}
+
+tasks.withType {
+ useJUnitPlatform()
+}
+
+kapt {
+ correctErrorTypes = true
+ arguments {
+ arg("mapstruct.unmappedTargetPolicy", "ignore")
+ }
+}
+
+sourceSets {
+ main {
+ java {
+ srcDir("build/generated/sources/annotationProcessor/java/main")
+ }
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/gradle/wrapper/gradle-wrapper.jar b/BACKEND_V2/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a4b76b9
Binary files /dev/null and b/BACKEND_V2/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/BACKEND_V2/gradle/wrapper/gradle-wrapper.properties b/BACKEND_V2/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e2847c8
--- /dev/null
+++ b/BACKEND_V2/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/BACKEND_V2/gradlew b/BACKEND_V2/gradlew
new file mode 100755
index 0000000..f5feea6
--- /dev/null
+++ b/BACKEND_V2/gradlew
@@ -0,0 +1,252 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/BACKEND_V2/gradlew.bat b/BACKEND_V2/gradlew.bat
new file mode 100644
index 0000000..9d21a21
--- /dev/null
+++ b/BACKEND_V2/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/BACKEND_V2/settings.gradle.kts b/BACKEND_V2/settings.gradle.kts
new file mode 100644
index 0000000..4189a93
--- /dev/null
+++ b/BACKEND_V2/settings.gradle.kts
@@ -0,0 +1,3 @@
+rootProject.name = "backend"
+gradle.startParameter.warningMode = WarningMode.None
+
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/BackendV2Application.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/BackendV2Application.kt
new file mode 100644
index 0000000..623d2d3
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/BackendV2Application.kt
@@ -0,0 +1,14 @@
+package ru.codebattles.backend
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.runApplication
+import ru.codebattles.backend.core.properties.JwtTokenProperties
+
+@SpringBootApplication
+@EnableConfigurationProperties(JwtTokenProperties::class)
+class BackendV2Application
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/Checked.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/Checked.kt
new file mode 100644
index 0000000..bc4481c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/Checked.kt
@@ -0,0 +1,6 @@
+package ru.codebattles.backend.annotations
+
+/*
+* Annotation for mark method as checked for security
+ */
+annotation class Checked(val comment: String = "")
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionAccessRequired.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionAccessRequired.kt
new file mode 100644
index 0000000..54c1807
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionAccessRequired.kt
@@ -0,0 +1,5 @@
+package ru.codebattles.backend.annotations
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CompetitionAccessRequired
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionId.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionId.kt
new file mode 100644
index 0000000..2618996
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/annotations/CompetitionId.kt
@@ -0,0 +1,5 @@
+package ru.codebattles.backend.annotations
+
+@Target(AnnotationTarget.VALUE_PARAMETER)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CompetitionId
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/aspects/CompetitionAccessAspect.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/aspects/CompetitionAccessAspect.kt
new file mode 100644
index 0000000..a9e8cb8
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/aspects/CompetitionAccessAspect.kt
@@ -0,0 +1,46 @@
+package ru.codebattles.backend.aspects
+
+import org.aspectj.lang.JoinPoint
+import org.aspectj.lang.annotation.Aspect
+import org.aspectj.lang.annotation.Before
+import org.aspectj.lang.reflect.MethodSignature
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.stereotype.Component
+import ru.codebattles.backend.annotations.CompetitionId
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.services.CompetitionService
+
+@Aspect
+@Component
+class CompetitionAccessAspect(
+ private val competitionService: CompetitionService,
+) {
+ @Before("@annotation(ru.codebattles.backend.annotations.CompetitionAccessRequired)")
+ fun checkAccess(joinPoint: JoinPoint) {
+ val authentication = SecurityContextHolder.getContext().authentication
+ if (authentication == null || !authentication.isAuthenticated) {
+ throw IllegalStateException("User is not authenticated!")
+ }
+ val user: User = authentication.principal as User
+
+
+ val method = (joinPoint.signature as MethodSignature).method
+ val parameterAnnotations = method.parameterAnnotations
+ val args = joinPoint.args
+
+ for ((index, annotations) in parameterAnnotations.withIndex()) {
+ if (annotations.any { it is CompetitionId }) {
+ val competitionId = args[index] as? Long
+ ?: throw IllegalArgumentException("Invalid competition ID")
+
+ if (!competitionService.checkAccessForCompetitionByUser(user, competitionId)) {
+ throw AccessDeniedException("No access to competition $competitionId")
+ }
+ return
+ }
+ }
+
+ throw IllegalStateException("No parameter annotated with @CompetitionId found")
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultAdminCommandRunner.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultAdminCommandRunner.kt
new file mode 100644
index 0000000..208a49b
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultAdminCommandRunner.kt
@@ -0,0 +1,39 @@
+package ru.codebattles.backend.core.commandrunner
+
+import org.springframework.boot.CommandLineRunner
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.stereotype.Component
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.entity.UserRole
+import ru.codebattles.backend.entity.Variable
+import ru.codebattles.backend.repository.UserRepository
+import ru.codebattles.backend.repository.VariablesRepository
+
+@Component
+class DefaultAdminCommandRunner(
+ val variablesRepository: VariablesRepository,
+ val userRepository: UserRepository,
+ val passwordEncoder: PasswordEncoder,
+) : CommandLineRunner {
+
+ val VARIABLE_KEY: String = "DEFAULT_USER_EXECUTOR_COMPLETE"
+
+
+ override fun run(vararg args: String?) {
+ val notFirstExecute = variablesRepository.existsByKey(VARIABLE_KEY)
+ val userWithUsernameAdminExists = userRepository.existsByMusername("admin")
+
+ if (notFirstExecute) {
+ return
+ }
+
+ if (!userWithUsernameAdminExists) {
+ val user = User(mpassword = passwordEncoder.encode("admin"), musername = "admin")
+ user.roles = mutableSetOf(UserRole.ROLE_ADMIN, UserRole.USER)
+
+ userRepository.save(user)
+ }
+
+ variablesRepository.save(Variable(key = VARIABLE_KEY, value = "true"))
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultCheckerCommandRunner.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultCheckerCommandRunner.kt
new file mode 100644
index 0000000..94213b0
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/commandrunner/DefaultCheckerCommandRunner.kt
@@ -0,0 +1,32 @@
+package ru.codebattles.backend.core.commandrunner
+
+import org.springframework.boot.CommandLineRunner
+import org.springframework.stereotype.Component
+import ru.codebattles.backend.entity.Checker
+import ru.codebattles.backend.entity.Variable
+import ru.codebattles.backend.repository.CheckerRepository
+import ru.codebattles.backend.repository.VariablesRepository
+
+@Component
+class DefaultCheckerCommandRunner(
+ val variablesRepository: VariablesRepository,
+ private val checkerRepository: CheckerRepository,
+) : CommandLineRunner {
+
+ val VARIABLE_KEY: String = "DEFAULT_CHECKER_EXECUTOR_COMPLETE"
+
+
+ override fun run(vararg args: String?) {
+ val notFirstExecute = variablesRepository.existsByKey(VARIABLE_KEY)
+ if (notFirstExecute) return
+
+ val checker = Checker(
+ displayName = "Default Python3 Checker",
+ languageHighlightName = "python",
+ address = "http://checker-python:7070/api/v1/test"
+ )
+
+ checkerRepository.save(checker)
+ variablesRepository.save(Variable(key = VARIABLE_KEY, value = "true"))
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/OpenApiConfig.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/OpenApiConfig.kt
new file mode 100644
index 0000000..e3954d3
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/OpenApiConfig.kt
@@ -0,0 +1,30 @@
+package ru.codebattles.backend.core.config
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
+import io.swagger.v3.oas.annotations.info.Contact
+import io.swagger.v3.oas.annotations.info.Info
+import io.swagger.v3.oas.annotations.security.SecurityScheme
+
+@OpenAPIDefinition(
+ info = Info(
+ title = "Codebattles backend",
+ description = """
+ This is a backend for the Codebattles competition system.
+ It is designed to handle various aspects of the competition, including user management, problem management, and competition management.
+ The system allows users to register, login, and participate in competitions.
+
+ Tips:
+ - [ADMIN] - Requires admin role
+ """,
+ version = "0.1.0",
+ contact = Contact(name = "Suslov Yaroslav", email = "genius@doctorixx.ru")
+ )
+)
+@SecurityScheme(
+ name = "JWT",
+ type = SecuritySchemeType.HTTP,
+ bearerFormat = "JWT",
+ scheme = "bearer"
+)
+class OpenApiConfig
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/PasswordEncoderConfig.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/PasswordEncoderConfig.kt
new file mode 100644
index 0000000..0ba7e57
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/PasswordEncoderConfig.kt
@@ -0,0 +1,15 @@
+package ru.codebattles.backend.core.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.crypto.password.PasswordEncoder
+
+@Configuration
+class PasswordEncoderConfig {
+ @Bean
+ fun encoder(): PasswordEncoder {
+ return BCryptPasswordEncoder()
+ }
+
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/SecurityConfig.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/SecurityConfig.kt
new file mode 100644
index 0000000..b6bcb3d
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/config/SecurityConfig.kt
@@ -0,0 +1,79 @@
+package ru.codebattles.backend.core.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpStatus
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.security.web.authentication.HttpStatusEntryPoint
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.web.cors.CorsConfiguration
+import org.springframework.web.cors.CorsConfigurationSource
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource
+import ru.codebattles.backend.core.filter.JwtAuthenticationFilter
+
+
+@Configuration
+@EnableMethodSecurity(jsr250Enabled = true)
+class SecurityConfig(
+ private val jwtAuthenticationFilter: JwtAuthenticationFilter,
+) {
+
+ @Bean
+ fun authenticationManager(authConfig: AuthenticationConfiguration): AuthenticationManager {
+ return authConfig.authenticationManager
+ }
+
+ @Bean
+ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+ http.csrf().disable()
+ .cors { cors ->
+ cors.configurationSource { request ->
+ CorsConfiguration().applyPermitDefaultValues().also {
+ it.allowedOriginPatterns = listOf("*")
+ it.allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
+ it.allowedHeaders = listOf("*")
+ it.allowCredentials = true
+ }
+ }
+ }
+ .authorizeRequests()
+// .requestMatchers("/api/auth/**").permitAll()
+ .requestMatchers(
+ "/api/ping",
+ "/api/auth/login",
+ "/api/auth/register",
+ "/api/check_system_callback/**",
+ "/swagger-ui/**",
+ "/v3/api-docs/**",
+ "/swagger-ui.html",
+ "/favicon.ico",
+ "/webjars/**"
+ ).permitAll()
+ .requestMatchers(
+ "/api/**"
+ ).authenticated()
+// .anyRequest().permitAll()
+ .and()
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
+ .exceptionHandling()
+ .authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
+ return http.build()
+ }
+
+ @Bean
+ fun corsConfigurationSource(): CorsConfigurationSource {
+ val configuration = CorsConfiguration()
+ configuration.allowedOrigins = listOf("http://localhost:5173")
+ configuration.allowedMethods = listOf("*")
+ configuration.allowedHeaders = listOf("*")
+ val source = UrlBasedCorsConfigurationSource()
+ source.registerCorsConfiguration("/**", configuration)
+ return source
+ }
+
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/filter/JwtAuthenticationFilter.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/filter/JwtAuthenticationFilter.kt
new file mode 100644
index 0000000..a46e013
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/filter/JwtAuthenticationFilter.kt
@@ -0,0 +1,38 @@
+package ru.codebattles.backend.core.filter
+
+import jakarta.servlet.FilterChain
+import jakarta.servlet.http.HttpServletRequest
+import jakarta.servlet.http.HttpServletResponse
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
+import org.springframework.stereotype.Component
+import org.springframework.web.filter.OncePerRequestFilter
+import ru.codebattles.backend.services.JwtService
+import ru.codebattles.backend.services.UserService
+
+@Component
+class JwtAuthenticationFilter(
+ private val jwtService: JwtService,
+ val userService: UserService
+) : OncePerRequestFilter() {
+
+ override fun doFilterInternal(
+ request: HttpServletRequest,
+ response: HttpServletResponse,
+ filterChain: FilterChain
+ ) {
+ val authHeader = request.getHeader("Authorization")
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ val token = authHeader.substring(7)
+ if (jwtService.validateToken(token)) {
+ val username = jwtService.getUsernameFromToken(token)
+ val user = userService.getByUsername(username)
+ val authentication = UsernamePasswordAuthenticationToken(user, null, user.authorities)
+ authentication.details = WebAuthenticationDetailsSource().buildDetails(request)
+ SecurityContextHolder.getContext().authentication = authentication
+ }
+ }
+ filterChain.doFilter(request, response)
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/properties/JwtTokenProperties.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/properties/JwtTokenProperties.kt
new file mode 100644
index 0000000..8dba134
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/core/properties/JwtTokenProperties.kt
@@ -0,0 +1,8 @@
+package ru.codebattles.backend.core.properties
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties("codebattles.jwt")
+data class JwtTokenProperties (
+ val secretKey: String?,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/AnswerDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/AnswerDto.kt
new file mode 100644
index 0000000..467cb2b
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/AnswerDto.kt
@@ -0,0 +1,38 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+import ru.codebattles.backend.entity.AnswerStatus
+import java.util.*
+
+data class AnswerDto(
+ @Schema(description = "Unique identifier of the answer", example = "1")
+ val id: Long? = null,
+
+ @Schema(description = "User who submitted the answer")
+ val user: UserDto,
+
+ @Schema(
+ description = "Current status of the answer",
+ example = "IN_PROGRESS",
+ )
+ val status: AnswerStatus = AnswerStatus.IN_PROGRESS,
+
+ @Schema(description = "Score awarded for the answer", example = "100")
+ val score: Int? = null,
+
+ @Schema(description = "Code submitted by the user", example = "print('Hello, World!')")
+ val code: String,
+
+ @Schema(description = "Result of the code execution", example = "Success")
+ val result: String? = null,
+
+ @Schema(description = "Checker used to evaluate the answer")
+ val checker: CheckerDto,
+
+ @Schema(description = "Timestamp when the answer was created", example = "2023-01-01T12:00:00Z")
+ val createdAt: Date,
+
+ @Schema(description = "Details of the competition problem associated with the answer")
+ val competitionsProblems: CompetitionsProblemsDto? = null
+)
+
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CheckerDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CheckerDto.kt
new file mode 100644
index 0000000..5e59509
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CheckerDto.kt
@@ -0,0 +1,14 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class CheckerDto(
+ @Schema(description = "Unique identifier of the checker", example = "1")
+ val id: Long? = null,
+
+ @Schema(description = "Display name of the checker", example = "Default Python3 Checker")
+ val displayName: String,
+
+ @Schema(description = "Programming language used by the checker", example = "python")
+ val languageHighlightName: String,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionCreateDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionCreateDto.kt
new file mode 100644
index 0000000..a893c78
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionCreateDto.kt
@@ -0,0 +1,18 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+import java.time.LocalDateTime
+
+data class CompetitionCreateDto(
+ @Schema(description = "Name of the competition", example = "Code Battles 2023")
+ val name: String,
+
+ @Schema(description = "Description of the competition", example = "A competitive coding event")
+ val description: String,
+
+ @Schema(description = "Start time of the competition", example = "2023-01-01T10:00:00")
+ val startedAt: LocalDateTime? = null,
+
+ @Schema(description = "End time of the competition", example = "2023-01-01T18:00:00")
+ val endedAt: LocalDateTime? = null
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionDto.kt
new file mode 100644
index 0000000..01e6328
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionDto.kt
@@ -0,0 +1,24 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+import java.time.LocalDateTime
+
+data class CompetitionDto(
+ @Schema(description = "Unique identifier of the competition", example = "1")
+ val id: Long,
+
+ @Schema(description = "Set of checkers associated with the competition")
+ val checkers: Set? = emptySet(),
+
+ @Schema(description = "Name of the competition", example = "Code Battles 2023")
+ val name: String,
+
+ @Schema(description = "Description of the competition", example = "A competitive coding event")
+ val description: String,
+
+ @Schema(description = "Start time of the competition", example = "2023-01-01T10:00:00")
+ val startedAt: LocalDateTime? = null,
+
+ @Schema(description = "End time of the competition", example = "2023-01-01T18:00:00")
+ val endedAt: LocalDateTime? = null
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionsProblemsDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionsProblemsDto.kt
new file mode 100644
index 0000000..1962585
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CompetitionsProblemsDto.kt
@@ -0,0 +1,17 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class CompetitionsProblemsDto(
+ @Schema(description = "Unique identifier of the competition problem", example = "1")
+ val id: Long,
+
+ @Schema(description = "Priority of problem for sorting", example = "12")
+ val priority: Int,
+
+ @Schema(description = "Unique identifier of the competition problem")
+ val problem: ProblemDto,
+
+ @Schema(description = "Unique identifier of the competition problem")
+ val slug: String,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateProblemDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateProblemDto.kt
new file mode 100644
index 0000000..405ffa6
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateProblemDto.kt
@@ -0,0 +1,23 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class CreateProblemDto(
+ @Schema(description = "Name of the problem", example = "Sum of Two Numbers")
+ val name: String,
+
+ @Schema(description = "Description of the problem", example = "Calculate the sum of two integers.")
+ val description: String,
+
+ @Schema(description = "Input data for the problem", example = "1 2")
+ val inData: String,
+
+ @Schema(description = "Expected output data for the problem", example = "3")
+ val outData: String,
+
+ @Schema(description = "Test cases for the problem (JSON)", example = """{"in":"1", "out":"3"}""")
+ val tests: String,
+
+ @Schema(description = "Example cases for the problem (JSON)", example = """{"in":"1", "out":"3"}""")
+ val examples: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateUserDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateUserDto.kt
new file mode 100644
index 0000000..2bc54bf
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/CreateUserDto.kt
@@ -0,0 +1,18 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+import jakarta.validation.constraints.NotEmpty
+import jakarta.validation.constraints.NotNull
+
+data class CreateUserDto(
+ @field:NotNull @field:NotEmpty
+ @Schema(description = "Username of the user", example = "john_doe")
+ val musername: String? = null,
+
+ @field:NotNull @field:NotEmpty
+ @Schema(description = "Password of the user", example = "securepassword123")
+ val mpassword: String? = null,
+
+ @Schema(description = "Full name of the user", example = "John Doe")
+ val name: String? = ""
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/ProblemDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/ProblemDto.kt
new file mode 100644
index 0000000..db7d2b3
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/ProblemDto.kt
@@ -0,0 +1,26 @@
+package ru.codebattles.backend.dto
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class ProblemDto(
+ @Schema(description = "Unique identifier of the problem", example = "1")
+ val id: Long,
+
+ @Schema(description = "Name of the problem", example = "Sum of Two Numbers")
+ val name: String,
+
+ @Schema(description = "Description of the problem", example = "Calculate the sum of two integers.")
+ val description: String,
+
+ @Schema(description = "Input data for the problem", example = "1 2")
+ val inData: String,
+
+ @Schema(description = "Expected output data for the problem", example = "3")
+ val outData: String,
+
+ @Schema(description = "Example cases for the problem", example = "Input: 1 2, Output: 3")
+ val examples: String,
+
+ @Schema(description = "Indicates if the problem is public", example = "true")
+ val public: Boolean? = false
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserDto.kt
new file mode 100644
index 0000000..3b51de4
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserDto.kt
@@ -0,0 +1,12 @@
+package ru.codebattles.backend.dto
+
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+data class UserDto(
+ @Schema(description = "Unique identifier of the user", example = "1")
+ val id: Long,
+
+ @Schema(description = "Username of the user", example = "john_doe")
+ val username: String,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileDto.kt
new file mode 100644
index 0000000..fee32a7
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileDto.kt
@@ -0,0 +1,8 @@
+package ru.codebattles.backend.dto
+
+data class UserProfileDto(
+ val id: Long,
+ val username: String,
+ val name: String,
+ val roles: List,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileEditDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileEditDto.kt
new file mode 100644
index 0000000..fb07331
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserProfileEditDto.kt
@@ -0,0 +1,5 @@
+package ru.codebattles.backend.dto
+
+data class UserProfileEditDto(
+ val name: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/AnswerMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/AnswerMapper.kt
new file mode 100644
index 0000000..267d52c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/AnswerMapper.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.AnswerDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.Answer
+
+@Mapper(componentModel = "spring")
+interface AnswerMapper : AbstractMapper
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CheckerMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CheckerMapper.kt
new file mode 100644
index 0000000..98a802f
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CheckerMapper.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.CheckerDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.Checker
+
+@Mapper(componentModel = "spring")
+interface CheckerMapper : AbstractMapper
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsMapper.kt
new file mode 100644
index 0000000..c93205c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsMapper.kt
@@ -0,0 +1,16 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.BeanMapping
+import org.mapstruct.Mapper
+import org.mapstruct.MappingTarget
+import org.mapstruct.NullValuePropertyMappingStrategy
+import ru.codebattles.backend.entity.Competition
+import ru.codebattles.backend.dto.CompetitionDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.web.entity.CompetitionEditDto
+
+@Mapper(componentModel = "spring")
+interface CompetitionsMapper : AbstractMapper {
+ @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+ fun update(dto: CompetitionEditDto?, @MappingTarget entity: Competition?)
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsProblemsMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsProblemsMapper.kt
new file mode 100644
index 0000000..f7db312
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CompetitionsProblemsMapper.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.CompetitionsProblemsDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.CompetitionsProblems
+
+@Mapper(componentModel = "spring")
+interface CompetitionsProblemsMapper : AbstractMapper
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CreateProblemsMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CreateProblemsMapper.kt
new file mode 100644
index 0000000..4794ce1
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/CreateProblemsMapper.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.CreateProblemDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.Problem
+
+@Mapper(componentModel = "spring")
+interface CreateProblemsMapper : AbstractMapper
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/ProblemsMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/ProblemsMapper.kt
new file mode 100644
index 0000000..bcebcc4
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/ProblemsMapper.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.ProblemDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.Problem
+
+@Mapper(componentModel = "spring")
+interface ProblemsMapper : AbstractMapper
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserMapper.kt
new file mode 100644
index 0000000..212e570
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserMapper.kt
@@ -0,0 +1,17 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.BeanMapping
+import org.mapstruct.Mapper
+import org.mapstruct.MappingTarget
+import org.mapstruct.NullValuePropertyMappingStrategy
+import ru.codebattles.backend.dto.UserDto
+import ru.codebattles.backend.dto.UserProfileEditDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.User
+
+
+@Mapper(componentModel = "spring")
+interface UserMapper : AbstractMapper {
+ @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+ fun updateUserProfile(dto: UserProfileEditDto?, @MappingTarget entity: User?)
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserProfileMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserProfileMapper.kt
new file mode 100644
index 0000000..af4e00a
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/UserProfileMapper.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.dto.mapper
+
+import org.mapstruct.Mapper
+import ru.codebattles.backend.dto.UserProfileDto
+import ru.codebattles.backend.dto.mapper.core.AbstractMapper
+import ru.codebattles.backend.entity.User
+
+
+@Mapper(componentModel = "spring")
+interface UserProfileMapper : AbstractMapper
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/core/AbstractMapper.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/core/AbstractMapper.kt
new file mode 100644
index 0000000..80c043a
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/mapper/core/AbstractMapper.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.dto.mapper.core
+
+
+interface AbstractMapper {
+ fun toDto(obj: OBJ): DTO
+ fun fromDto(obj: DTO): OBJ
+ fun fromDtoS(obj: List): List
+ fun toDtoS(obj: List): List
+ fun toDtoS(obj: Set): List
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Answer.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Answer.kt
new file mode 100644
index 0000000..406c060
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Answer.kt
@@ -0,0 +1,32 @@
+package ru.codebattles.backend.entity
+
+
+import jakarta.persistence.*
+
+enum class AnswerStatus {
+ IN_PROGRESS,
+ COMPLETED,
+}
+
+@Entity
+data class Answer(
+ @ManyToOne
+ val competition: Competition,
+ @ManyToOne
+ val user: User,
+ @Enumerated(EnumType.STRING)
+ var status: AnswerStatus = AnswerStatus.IN_PROGRESS,
+ var score: Int? = null,
+
+ val code: String,
+
+ @ManyToOne
+ val checker: Checker,
+
+ var result: String? = null,
+
+ @ManyToOne
+ val competitionsProblems: CompetitionsProblems? = null
+
+ ) : BaseEntity()
+
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/BaseEntity.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/BaseEntity.kt
new file mode 100644
index 0000000..8536ee2
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/BaseEntity.kt
@@ -0,0 +1,42 @@
+package ru.codebattles.backend.entity
+
+import jakarta.persistence.*
+import lombok.Getter
+import lombok.Setter
+import org.hibernate.annotations.CreationTimestamp
+import org.hibernate.annotations.UpdateTimestamp
+import java.io.Serializable
+import java.util.*
+
+@Getter
+@Setter
+@MappedSuperclass
+abstract class BaseEntity : Serializable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long? = null
+
+
+ @CreationTimestamp
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "created_at")
+ val createdAt: Date? = null
+
+ @UpdateTimestamp
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "updated_at")
+ val updatedAt: Date? = null
+
+
+ override fun hashCode(): Int {
+ return Objects.hash(id)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as BaseEntity
+ return id == other.id
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Checker.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Checker.kt
new file mode 100644
index 0000000..2492749
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Checker.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.entity
+
+import jakarta.persistence.Entity
+
+@Entity
+data class Checker(
+ val displayName: String,
+ val languageHighlightName: String,
+ val address: String,
+) : BaseEntity()
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Competition.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Competition.kt
new file mode 100644
index 0000000..2802746
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Competition.kt
@@ -0,0 +1,39 @@
+package ru.codebattles.backend.entity
+
+import jakarta.persistence.*
+import java.util.*
+
+@Entity
+@Table(name = "competitions")
+data class Competition(
+ @ManyToMany
+ var members: MutableSet? = mutableSetOf(),
+
+ @ManyToMany
+ var checkers: MutableSet? = mutableSetOf(),
+
+ @ManyToOne
+ var organizer: User?,
+
+ @Column(nullable = false)
+ var name: String,
+
+ @Column(nullable = false, length = 1000)
+ var description: String,
+
+ @Column(name = "started_at")
+ var startedAt: Date? = null,
+
+ @Column(name = "ended_at")
+ var endedAt: Date? = null,
+
+ @Column(name = "show_rating", nullable = false)
+ var showRating: Boolean = true,
+
+ @Column(name = "show_output", nullable = false)
+ var showOutput: Boolean = true,
+
+ @Column(name = "show_input", nullable = false)
+ var showInput: Boolean = true,
+
+ ) : BaseEntity()
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/CompetitionsProblems.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/CompetitionsProblems.kt
new file mode 100644
index 0000000..db26bcd
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/CompetitionsProblems.kt
@@ -0,0 +1,16 @@
+package ru.codebattles.backend.entity
+
+import jakarta.persistence.Entity
+import jakarta.persistence.ManyToOne
+
+@Entity
+data class CompetitionsProblems(
+ val priority: Int,
+
+ val slug: String,
+
+ @ManyToOne
+ val competition: Competition,
+ @ManyToOne
+ val problem: Problem,
+) : BaseEntity()
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/LeaderBoardAllTasksQuery.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/LeaderBoardAllTasksQuery.kt
new file mode 100644
index 0000000..e76b762
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/LeaderBoardAllTasksQuery.kt
@@ -0,0 +1,23 @@
+package ru.codebattles.backend.entity
+
+import java.util.*
+
+data class LeaderBoardAllTasksQuery(
+ val userId: Long,
+ val competitionproblemId: Long,
+ val maxScore: Long
+
+)
+
+data class LeaderBoardScoreOrderQuery(
+ val userId: Long,
+ val userX: String,
+ val score: Long,
+ val time: Date
+
+)
+
+data class Leaderboard(
+ val score: List,
+ val data: Map>
+)
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Problem.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Problem.kt
new file mode 100644
index 0000000..a71ac30
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Problem.kt
@@ -0,0 +1,27 @@
+package ru.codebattles.backend.entity
+
+import jakarta.persistence.*
+
+
+@Entity
+data class Problem(
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long = 0,
+
+ val name: String,
+
+ @Column(name = "description", columnDefinition = "TEXT")
+ val description: String,
+
+ @Column(name = "in_data", columnDefinition = "TEXT")
+ val inData: String,
+
+ @Column(name = "out_data", columnDefinition = "TEXT")
+ val outData: String,
+
+ @Column(name = "tests", columnDefinition = "TEXT")
+ val tests: String,
+
+ @Column(name = "examples", columnDefinition = "TEXT")
+ val examples: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/User.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/User.kt
new file mode 100644
index 0000000..eddc589
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/User.kt
@@ -0,0 +1,51 @@
+package ru.codebattles.backend.entity
+
+
+import jakarta.persistence.*
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+@Entity
+@Table(name = "users_")
+data class User(
+ @Column(name = "username")
+ var musername: String?,
+ @Column(name = "password")
+ var mpassword: String?,
+
+ var name: String? = "",
+
+ @ElementCollection(targetClass = UserRole::class, fetch = FetchType.EAGER)
+ @CollectionTable(
+ name = "users_roles",
+ joinColumns = [JoinColumn(name = "user_id")]
+ )
+ @Enumerated(EnumType.STRING)
+ @Column(name = "role")
+ var roles: MutableSet = mutableSetOf(),
+
+ ) : UserDetails, BaseEntity() {
+ override fun getAuthorities(): MutableCollection {
+ return roles.map { SimpleGrantedAuthority(it.name) }.toMutableList()
+ }
+
+ override fun getPassword() = mpassword
+ override fun getUsername() = musername
+
+
+ fun isAdmin(): Boolean {
+ return authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN")) || authorities.contains(SimpleGrantedAuthority("ADMIN"))
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is User) return false
+ return this.id == other.id
+ }
+
+ override fun hashCode(): Int {
+ return id?.hashCode() ?: 0
+ }
+
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/UserRole.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/UserRole.kt
new file mode 100644
index 0000000..86028b4
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/UserRole.kt
@@ -0,0 +1,8 @@
+package ru.codebattles.backend.entity
+
+enum class UserRole {
+ USER,
+ ROLE_ADMIN,
+ TESTTTSTCBEUYBEYUBYUCYUBYUBUBY,
+ SYSADMIN
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Variable.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Variable.kt
new file mode 100644
index 0000000..491d132
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/entity/Variable.kt
@@ -0,0 +1,17 @@
+package ru.codebattles.backend.entity
+
+
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "variables")
+data class Variable(
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long = 0,
+
+ @Column(name = "name", unique = true, nullable = false)
+ val key: String,
+
+ @Column(name = "value", nullable = true)
+ val value: String?,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/AnswerRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/AnswerRepository.kt
new file mode 100644
index 0000000..546431c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/AnswerRepository.kt
@@ -0,0 +1,12 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.Answer
+import ru.codebattles.backend.entity.User
+
+interface AnswerRepository : JpaRepository {
+ fun getAllByUserAndCompetitionId(user: User, compId: Long): List
+ fun getAllByUserIdAndCompetitionId(userId: Long, compId: Long): List
+ fun getFirstByUserIdAndCompetitionsProblemsIdOrderByCreatedAtDesc(userId: Long, compProbId: Long): Answer
+
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CheckerRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CheckerRepository.kt
new file mode 100644
index 0000000..8602491
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CheckerRepository.kt
@@ -0,0 +1,7 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.Checker
+
+interface CheckerRepository : JpaRepository {
+ fun findByIdIn(id: Set): MutableSet}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionProblemsRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionProblemsRepository.kt
new file mode 100644
index 0000000..f4d9a90
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionProblemsRepository.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.CompetitionsProblems
+
+interface CompetitionProblemsRepository : JpaRepository {
+ fun getAllByCompetitionId(id: Long): List
+ fun getFirstByCompetitionIdAndProblemId(competition_id: Long, problem_id: Long): CompetitionsProblems
+ fun getFirstByCompetitionIdAndId(competition_id: Long, id: Long): CompetitionsProblems
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionRepository.kt
new file mode 100644
index 0000000..d74c2cb
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/CompetitionRepository.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.Competition
+import ru.codebattles.backend.entity.User
+
+interface CompetitionRepository : JpaRepository {
+ fun getByMembersContaining(user: User): List
+ fun existsByIdAndMembersId(id: Long, memberId: Long): Boolean
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/LeaderboardRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/LeaderboardRepository.kt
new file mode 100644
index 0000000..cbd4383
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/LeaderboardRepository.kt
@@ -0,0 +1,51 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.query.Param
+import ru.codebattles.backend.entity.Competition
+import ru.codebattles.backend.entity.LeaderBoardAllTasksQuery
+import ru.codebattles.backend.entity.LeaderBoardScoreOrderQuery
+
+interface LeaderboardRepository : JpaRepository {
+ @Query(
+ """
+ SELECT
+ a.user_id AS userId,
+ cp.id AS competitionProblemID,
+ MAX(a.score) AS maxScore
+ FROM public.competitions_problems AS cp
+ JOIN answer a ON a.competitions_problems_id = cp.id
+ WHERE cp.competition_id = :compId
+ GROUP BY a.user_id, cp.id
+ """,
+ nativeQuery = true
+ )
+ fun getLeaderboard(@Param("compId") compId: Long): List
+
+ @Query(
+ """
+ SELECT
+ userId,
+ userX,
+ SUM(maxScore) AS score,
+ MAX(maxTime) AS time
+ FROM (
+ SELECT
+ a.user_id AS userId,
+ COALESCE(u.name, 'no name') AS userX,
+ MAX(a.score) AS maxScore,
+ MAX(a.created_at) AS maxTime
+ FROM public.competitions_problems AS cp
+ JOIN answer a ON a.competitions_problems_id = cp.id
+ JOIN users_ u ON a.user_id = u.id
+ WHERE cp.competition_id = :compId
+ GROUP BY a.user_id, cp.id, u.name
+ ) AS subquery
+ GROUP BY userId, userX
+ ORDER BY score DESC, time
+""",
+ nativeQuery = true
+ )
+ fun getLeaderboardStats(@Param("compId") compId: Long): List
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/ProblemsRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/ProblemsRepository.kt
new file mode 100644
index 0000000..c4aa61d
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/ProblemsRepository.kt
@@ -0,0 +1,6 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.Problem
+
+interface ProblemsRepository : JpaRepository
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/UserRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/UserRepository.kt
new file mode 100644
index 0000000..d83acac
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/UserRepository.kt
@@ -0,0 +1,13 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+import ru.codebattles.backend.entity.User
+
+
+@Repository
+interface UserRepository : JpaRepository {
+ fun findByMusername(username: String): User
+ fun findByIdIn(ids: Set): MutableSet
+ fun existsByMusername(username: String): Boolean
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/VariablesRepository.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/VariablesRepository.kt
new file mode 100644
index 0000000..251d55a
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/repository/VariablesRepository.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.repository
+
+import org.springframework.data.jpa.repository.JpaRepository
+import ru.codebattles.backend.entity.Variable
+
+
+interface VariablesRepository : JpaRepository {
+ fun findByKey(key: String): Variable?
+ fun existsByKey(key: String): Boolean
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/AnswerService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/AnswerService.kt
new file mode 100644
index 0000000..22449d6
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/AnswerService.kt
@@ -0,0 +1,76 @@
+package ru.codebattles.backend.services
+
+import com.fasterxml.jackson.core.type.TypeReference
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.springframework.stereotype.Service
+import ru.codebattles.backend.dto.AnswerDto
+import ru.codebattles.backend.dto.mapper.AnswerMapper
+import ru.codebattles.backend.entity.Answer
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.AnswerRepository
+import ru.codebattles.backend.repository.CheckerRepository
+import ru.codebattles.backend.repository.CompetitionProblemsRepository
+import ru.codebattles.backend.repository.CompetitionRepository
+import ru.codebattles.backend.web.entity.SendAnswerRequest
+import ru.codebattles.backend.web.entity.checker.CheckerTaskRequest
+import ru.codebattles.backend.web.entity.checker.Test
+
+@Service
+class AnswerService(
+ val answerRepository: AnswerRepository,
+ val checkerRepository: CheckerRepository,
+ val competitionRepository: CompetitionRepository,
+ val competitionProblemsRepository: CompetitionProblemsRepository,
+ val checkerApiService: CheckerApiService,
+ val answerMapper: AnswerMapper,
+ val objectMapper: ObjectMapper
+) {
+ fun createAnswer(user: User, data: SendAnswerRequest) {
+ val checker = checkerRepository.findById(data.checker).orElseThrow()
+ val competitionProblem = competitionProblemsRepository.findById(data.id).orElseThrow()
+ val savedAnswer = answerRepository.save(
+ Answer(
+ competition = competitionProblem.competition,
+ checker = checker,
+ user = user,
+ code = data.src,
+ competitionsProblems = competitionProblem
+ )
+ )
+
+ val request = CheckerTaskRequest(
+ source = data.src,
+ compiler = "python",
+ tests = objectMapper.readValue(competitionProblem.problem.tests,
+ object : TypeReference>() {}
+ ),
+ savedAnswer.id.toString()
+ )
+
+ checkerApiService.sendCheckerTask(request, checker.address)
+ }
+
+ fun getAllAnswersByCompetitionsAndUser(competition: Long, user: User): List {
+ return answerMapper.toDtoS(
+ answerRepository.getAllByUserAndCompetitionId(user, competition)
+ )
+ }
+
+ fun getAllAnswersByCompetitionsAndUserId(competition: Long, userId: Long): List {
+ return answerMapper.toDtoS(
+ answerRepository.getAllByUserIdAndCompetitionId(userId, competition)
+ )
+ }
+
+ fun getLastByUserIdAndCompetitionsAnswerId(userId: Long, competitionProblemId: Long): AnswerDto {
+ return answerMapper.toDto(
+ answerRepository.getFirstByUserIdAndCompetitionsProblemsIdOrderByCreatedAtDesc(userId, competitionProblemId)
+ )
+ }
+
+ fun getById(id: Long): AnswerDto {
+ return answerMapper.toDto(
+ answerRepository.findById(id).orElseThrow()
+ )
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerApiService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerApiService.kt
new file mode 100644
index 0000000..5519e0a
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerApiService.kt
@@ -0,0 +1,29 @@
+package ru.codebattles.backend.services
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import org.springframework.stereotype.Service
+import org.springframework.web.client.RestTemplate
+import ru.codebattles.backend.web.entity.checker.CheckerTaskRequest
+
+
+@Service
+class CheckerApiService(
+ val objectMapper: ObjectMapper,
+) {
+
+ fun sendCheckerTask(payload: CheckerTaskRequest, url: String) {
+ val restTemplate = RestTemplate()
+
+ val headers = HttpHeaders()
+ headers.contentType = MediaType.APPLICATION_JSON
+ val json = objectMapper.writeValueAsString(payload)
+ val entity = HttpEntity(json, headers)
+ restTemplate.postForEntity(
+ url, entity,
+ String::class.java
+ )
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerService.kt
new file mode 100644
index 0000000..a70152f
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CheckerService.kt
@@ -0,0 +1,40 @@
+package ru.codebattles.backend.services
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.springframework.http.HttpStatus
+import org.springframework.stereotype.Service
+import org.springframework.web.server.ResponseStatusException
+import ru.codebattles.backend.entity.Checker
+import ru.codebattles.backend.repository.CheckerRepository
+import java.io.IOException
+
+@Service
+class CheckerService(
+ private val checkerRepository: CheckerRepository,
+ private val objectMapper: ObjectMapper,
+) {
+
+
+ @Throws(IOException::class)
+ fun patch(id: Long, patchNode: JsonNode): Checker {
+ val checker: Checker = checkerRepository.findById(id).orElseThrow {
+ ResponseStatusException(HttpStatus.NOT_FOUND, "Entity with id `$id` not found")
+ }
+ objectMapper.readerForUpdating(checker).readValue(patchNode)
+ return checkerRepository.save(checker)
+ }
+
+ fun create(checker: Checker): Checker {
+ return checkerRepository.save(checker)
+ }
+
+
+ fun delete(id: Long): Checker? {
+ val checker: Checker? = checkerRepository.findById(id).orElse(null)
+ if (checker != null) {
+ checkerRepository.delete(checker)
+ }
+ return checker
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionService.kt
new file mode 100644
index 0000000..2bf97ad
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionService.kt
@@ -0,0 +1,138 @@
+package ru.codebattles.backend.services
+
+import org.springframework.http.HttpStatus
+import org.springframework.stereotype.Service
+import org.springframework.web.server.ResponseStatusException
+import ru.codebattles.backend.dto.CompetitionCreateDto
+import ru.codebattles.backend.dto.CompetitionDto
+import ru.codebattles.backend.dto.CompetitionsProblemsDto
+import ru.codebattles.backend.dto.UserDto
+import ru.codebattles.backend.dto.mapper.CompetitionsMapper
+import ru.codebattles.backend.dto.mapper.CompetitionsProblemsMapper
+import ru.codebattles.backend.dto.mapper.UserMapper
+import ru.codebattles.backend.entity.Competition
+import ru.codebattles.backend.entity.LeaderBoardAllTasksQuery
+import ru.codebattles.backend.entity.Leaderboard
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.*
+import java.util.stream.Collectors
+
+@Service
+class CompetitionService(
+ private val competitionRepository: CompetitionRepository,
+ private val userRepository: UserRepository,
+ private val userMapper: UserMapper,
+ private val competitionProblemsRepository: CompetitionProblemsRepository,
+ private val competitionsProblemsMapper: CompetitionsProblemsMapper,
+ private val competitionsMapper: CompetitionsMapper,
+ private val leaderboardRepository: LeaderboardRepository,
+ private val checkerRepository: CheckerRepository,
+) {
+
+ fun getAll(): List {
+ return competitionsMapper.toDtoS(
+ competitionRepository.findAll()
+ )
+ }
+
+ fun getById(id: Long): CompetitionDto {
+ val optionalCompetition = competitionRepository.findById(id)
+ if (optionalCompetition.isPresent) {
+ return competitionsMapper.toDto(optionalCompetition.get())
+ }
+
+
+ throw ResponseStatusException(HttpStatus.NOT_FOUND)
+ }
+
+ fun getByIdNotDto(id: Long): Competition {
+ val optionalCompetition = competitionRepository.findById(id)
+ if (optionalCompetition.isPresent) {
+ return optionalCompetition.get()
+ }
+
+
+ throw ResponseStatusException(HttpStatus.NOT_FOUND)
+ }
+
+ fun getProblemsById(id: Long): List {
+ return competitionsProblemsMapper.toDtoS(
+ competitionProblemsRepository.getAllByCompetitionId(id)
+ )
+
+ }
+
+ fun getLeaderboardById(id: Long): Leaderboard {
+ val leaderboard = leaderboardRepository.getLeaderboard(id)
+ val leaderboardScores = leaderboardRepository.getLeaderboardStats(id)
+
+ val answersByScore: Map> = leaderboard.stream()
+ .collect(Collectors.groupingBy(LeaderBoardAllTasksQuery::userId))
+
+ return Leaderboard(
+ data = answersByScore,
+ score = leaderboardScores
+ )
+ }
+
+ fun getProblemById(id: Long, problemId: Long): CompetitionsProblemsDto {
+ return competitionsProblemsMapper.toDto(
+ competitionProblemsRepository.getFirstByCompetitionIdAndId(id, problemId)
+ )
+ }
+
+ fun patchUsers(compId: Long, usersIds: Set) {
+ val competition = competitionRepository.findById(compId).orElseThrow()
+ competition.members = userRepository.findByIdIn(usersIds)
+ competitionRepository.save(competition)
+ }
+
+ fun patchCheckers(compId: Long, checkersIds: Set) {
+ val competition = competitionRepository.findById(compId).orElseThrow()
+ competition.checkers = checkerRepository.findByIdIn(checkersIds)
+ competitionRepository.save(competition)
+ }
+
+ fun joinUser(compId: Long, userId: Long) {
+ val competition = competitionRepository.findById(compId).orElseThrow()
+ val user = userRepository.getById(userId)
+ competition.members?.add(user)
+ competitionRepository.save(competition)
+ }
+
+ fun getUsers(compId: Long): List {
+ val competition = competitionRepository.findById(compId).orElseThrow()
+
+ return userMapper.toDtoS(competition.members!!)
+ }
+
+
+ fun getAllByUser(user: User): List {
+ return competitionsMapper.toDtoS(
+ competitionRepository.getByMembersContaining(user)
+ )
+ }
+
+ fun create(competitionDto: CompetitionCreateDto, user: User): CompetitionDto {
+ val competition = Competition(
+ organizer = user,
+ name = competitionDto.name,
+ description = competitionDto.description,
+ )
+
+ competitionRepository.save(
+ competition
+ )
+
+ return competitionsMapper.toDto(
+ competition
+ )
+ }
+
+ fun checkAccessForCompetitionByUser(user: User, competitionId: Long): Boolean {
+ return (
+ user.isAdmin() ||
+ competitionRepository.existsByIdAndMembersId(competitionId, user.id!!)
+ )
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionsProblemsService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionsProblemsService.kt
new file mode 100644
index 0000000..06248a8
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/CompetitionsProblemsService.kt
@@ -0,0 +1,84 @@
+package ru.codebattles.backend.services
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.springframework.data.domain.Page
+import org.springframework.data.domain.Pageable
+import org.springframework.http.HttpStatus
+import org.springframework.stereotype.Service
+import org.springframework.web.server.ResponseStatusException
+import ru.codebattles.backend.entity.CompetitionsProblems
+import ru.codebattles.backend.repository.CompetitionProblemsRepository
+import ru.codebattles.backend.web.entity.CreateCompetitionProblem
+import java.io.IOException
+import java.util.*
+
+@Service
+class CompetitionsProblemsService(
+ private val competitionProblemsRepository: CompetitionProblemsRepository,
+ private val objectMapper: ObjectMapper,
+ private val competitionService: CompetitionService,
+ private val problemsService: ProblemsService,
+) {
+
+
+ fun getAll(pageable: Pageable): Page {
+ return competitionProblemsRepository.findAll(pageable)
+ }
+
+ fun getOne(id: Long): CompetitionsProblems {
+ val competitionsProblemsOptional: Optional = competitionProblemsRepository.findById(id)
+ return competitionsProblemsOptional.orElseThrow {
+ ResponseStatusException(HttpStatus.NOT_FOUND, "Entity with id `$id` not found")
+ }
+ }
+
+ fun getMany(ids: List): List {
+ return competitionProblemsRepository.findAllById(ids)
+ }
+
+ fun create(data: CreateCompetitionProblem): CompetitionsProblems {
+
+ val competitionsProblems = CompetitionsProblems(
+ priority = data.priority.toInt(),
+ slug = data.slug,
+ competition = competitionService.getByIdNotDto(data.competition_id),
+ problem = problemsService.getByIdNotDto(data.problem_id),
+ )
+
+ return competitionProblemsRepository.save(competitionsProblems)
+ }
+
+ @Throws(IOException::class)
+ fun patch(id: Long, patchNode: JsonNode): CompetitionsProblems {
+ val competitionsProblems: CompetitionsProblems = competitionProblemsRepository.findById(id).orElseThrow {
+ ResponseStatusException(HttpStatus.NOT_FOUND, "Entity with id `$id` not found")
+ }
+ objectMapper.readerForUpdating(competitionsProblems).readValue(patchNode)
+ return competitionProblemsRepository.save(competitionsProblems)
+ }
+
+ @Throws(IOException::class)
+ fun patchMany(ids: List, patchNode: JsonNode): List {
+ val competitionsProblems: Collection = competitionProblemsRepository.findAllById(ids)
+ for (competitionsProblem in competitionsProblems) {
+ objectMapper.readerForUpdating(competitionsProblem).readValue(patchNode)
+ }
+ val resultCompetitionsProblems: List =
+ competitionProblemsRepository.saveAll(competitionsProblems)
+ return resultCompetitionsProblems.mapNotNull(CompetitionsProblems::id)
+ }
+
+ fun delete(id: Long): CompetitionsProblems? {
+ val competitionsProblems: CompetitionsProblems? = competitionProblemsRepository.findById(id).orElse(null)
+ if (competitionsProblems != null) {
+ competitionProblemsRepository.delete(competitionsProblems)
+ }
+ return competitionsProblems
+ }
+
+ fun deleteMany(ids: List) {
+ competitionProblemsRepository.deleteAllById(ids)
+ }
+
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/JwtService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/JwtService.kt
new file mode 100644
index 0000000..f1c49f7
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/JwtService.kt
@@ -0,0 +1,58 @@
+package ru.codebattles.backend.services
+
+
+import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.security.Keys
+import org.springframework.stereotype.Service
+import ru.codebattles.backend.core.properties.JwtTokenProperties
+import ru.codebattles.tools.generateSecureRandomString
+import java.security.Key
+import java.util.*
+import javax.crypto.SecretKey
+
+@Service
+class JwtService(
+ private val properties: JwtTokenProperties
+) {
+
+ private val jwtSecret: Key = getSecretKey()
+ private val jwtExpirationMs = 3600000 // 1 hour
+
+
+ private final fun getSecretKey(): SecretKey {
+ var secretKey = properties.secretKey
+ if (secretKey == null) secretKey = generateSecureRandomString(128)
+
+ val decodedKey = Base64.getDecoder().decode(secretKey)
+ return Keys.hmacShaKeyFor(decodedKey)
+ }
+
+ fun generateToken(username: String): String {
+ return Jwts.builder()
+ .setSubject(username)
+ .setIssuedAt(Date())
+ .setExpiration(Date(System.currentTimeMillis() + jwtExpirationMs))
+ .signWith(jwtSecret)
+ .compact()
+ }
+
+ fun validateToken(token: String): Boolean {
+ try {
+ val claims = Jwts.parserBuilder()
+ .setSigningKey(jwtSecret)
+ .build()
+ .parseClaimsJws(token)
+ return !claims.body.expiration.before(Date())
+ } catch (e: Exception) {
+ return false
+ }
+ }
+
+ fun getUsernameFromToken(token: String): String {
+ val claims = Jwts.parserBuilder()
+ .setSigningKey(jwtSecret)
+ .build()
+ .parseClaimsJws(token)
+ return claims.body.subject
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/ProblemsService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/ProblemsService.kt
new file mode 100644
index 0000000..99d96ce
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/ProblemsService.kt
@@ -0,0 +1,76 @@
+package ru.codebattles.backend.services
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.springframework.http.HttpStatus
+import org.springframework.stereotype.Service
+import org.springframework.web.server.ResponseStatusException
+import ru.codebattles.backend.dto.CreateProblemDto
+import ru.codebattles.backend.dto.ProblemDto
+import ru.codebattles.backend.dto.mapper.CreateProblemsMapper
+import ru.codebattles.backend.dto.mapper.ProblemsMapper
+import ru.codebattles.backend.entity.Problem
+import ru.codebattles.backend.repository.ProblemsRepository
+import java.io.IOException
+
+@Service
+class ProblemsService(
+ val problemsRepository: ProblemsRepository,
+ val problemsMapper: ProblemsMapper,
+ val createProblemsMapper: CreateProblemsMapper, private val objectMapper: ObjectMapper
+) {
+ fun getById(id: Long): ProblemDto {
+ val optionalProblem = problemsRepository.findById(id)
+ if (optionalProblem.isPresent) {
+ return problemsMapper.toDto(optionalProblem.get())
+ }
+
+
+ throw ResponseStatusException(HttpStatus.NOT_FOUND)
+ }
+
+ fun getByIdNotDto(id: Long): Problem {
+ val optionalProblem = problemsRepository.findById(id)
+ if (optionalProblem.isPresent) {
+ return optionalProblem.get()
+ }
+
+
+ throw ResponseStatusException(HttpStatus.NOT_FOUND)
+ }
+
+
+ fun create(problemDto: CreateProblemDto): ProblemDto {
+ val problem = Problem(
+ name = problemDto.name,
+ description = problemDto.description,
+ inData = problemDto.inData,
+ outData = problemDto.outData,
+ tests = problemDto.tests,
+ examples = problemDto.examples,
+ )
+
+ println()
+
+ val competition = problemsRepository.save(problem)
+
+ println()
+
+ return problemsMapper.toDto(competition)
+ }
+
+ @Throws(IOException::class)
+ fun patch(id: Long, patchNode: JsonNode): Problem {
+ val problem: Problem = problemsRepository.findById(id).orElseThrow {
+ ResponseStatusException(HttpStatus.NOT_FOUND, "Entity with id `$id` not found")
+ }
+ objectMapper.readerForUpdating(problem).readValue(patchNode)
+ return problemsRepository.save(problem)
+ }
+
+ fun getAll(): Iterable {
+ return problemsMapper.toDtoS(
+ problemsRepository.findAll()
+ )
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserDetailService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserDetailService.kt
new file mode 100644
index 0000000..d6d990c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserDetailService.kt
@@ -0,0 +1,15 @@
+package ru.codebattles.backend.services
+
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.stereotype.Component
+import ru.codebattles.backend.repository.UserRepository
+
+@Component
+class UserDetailService(
+ private val userRepository: UserRepository,
+) : UserDetailsService {
+ override fun loadUserByUsername(username: String?): UserDetails {
+ return userRepository.findByMusername(username!!)
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserService.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserService.kt
new file mode 100644
index 0000000..c6f2aef
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/services/UserService.kt
@@ -0,0 +1,32 @@
+package ru.codebattles.backend.services
+
+
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.stereotype.Service
+import ru.codebattles.backend.dto.CreateUserDto
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.entity.UserRole
+import ru.codebattles.backend.repository.UserRepository
+
+@Service
+class UserService(
+ private val userRepository: UserRepository,
+ private val passwordEncoder: PasswordEncoder,
+) {
+ fun getByUsername(username: String): User {
+ return userRepository.findByMusername(username)
+ }
+ fun create(userDto: CreateUserDto): User {
+
+ val user = User(
+ mpassword = passwordEncoder.encode(userDto.mpassword),
+ musername = userDto.musername,
+ name = userDto.name
+ )
+ user.roles = mutableSetOf(UserRole.USER)
+
+ userRepository.save(user)
+
+ return user
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AnswerController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AnswerController.kt
new file mode 100644
index 0000000..3a712d1
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AnswerController.kt
@@ -0,0 +1,57 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.Parameter
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import io.swagger.v3.oas.annotations.tags.Tag
+import jakarta.annotation.security.RolesAllowed
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.AnswerDto
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.entity.UserRole
+import ru.codebattles.backend.services.AnswerService
+
+@Tag(name = "Answer", description = "Endpoints for answers")
+@RestController
+@RequestMapping("/api/answers")
+@SecurityRequirement(name = "JWT")
+class AnswerController(
+ val answerService: AnswerService,
+) {
+
+ @Operation(
+ summary = "Get answer by ID",
+ description = "Retrieves answer details by its ID. Requires access to the answer."
+ )
+ @GetMapping("{id}")
+ fun getById(@PathVariable id: Long, @AuthenticationPrincipal user: User): AnswerDto {
+ val answer = answerService.getById(id)
+
+ if (user.roles.contains(UserRole.ROLE_ADMIN) || answer.user.id == user.id) {
+ return answer
+ }
+
+ throw AccessDeniedException("You do not have access to this answer")
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get the last answer by problem and user ID",
+ description = "Retrieves the most recent answer submitted by a specific user " +
+ "for a specific competition problem. Required admin role."
+ )
+ @GetMapping("/last")
+ @RolesAllowed("ADMIN")
+ fun getLastSendByProblemAnswerAndUserId(
+ @Parameter(description = "The ID of CompetitionProblem id", required = true)
+ @RequestParam
+ compProblemId: Long,
+
+ @Parameter(description = "The ID of the user", required = true)
+ @RequestParam
+ userId: Long,
+ ): AnswerDto {
+ return answerService.getLastByUserIdAndCompetitionsAnswerId(userId, compProblemId)
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AuthContoroller.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AuthContoroller.kt
new file mode 100644
index 0000000..b2e81e3
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/AuthContoroller.kt
@@ -0,0 +1,37 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import ru.codebattles.backend.services.JwtService
+import ru.codebattles.backend.web.entity.auth.AuthRequest
+import ru.codebattles.backend.web.entity.auth.AuthResponse
+
+@Tag(name = "Auth", description = "Endpoints for authentication")
+@RestController
+@RequestMapping("/api/auth")
+class AuthController(
+ private val authenticationManager: AuthenticationManager,
+ private val jwtService: JwtService
+) {
+
+ @Operation(
+ summary = "Login",
+ description = "Enter credentials to get JWT token."
+ )
+ @PostMapping("/login")
+ fun login(@RequestBody authRequest: AuthRequest): AuthResponse {
+ val authentication = authenticationManager.authenticate(
+ UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password)
+ )
+ val token = jwtService.generateToken(authentication.name)
+ return AuthResponse(token)
+ }
+}
+
+
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerController.kt
new file mode 100644
index 0000000..c5800ed
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerController.kt
@@ -0,0 +1,100 @@
+package ru.codebattles.backend.web.controllers
+
+import com.fasterxml.jackson.databind.JsonNode
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.tags.Tag
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import jakarta.annotation.security.RolesAllowed
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.CheckerDto
+import ru.codebattles.backend.dto.mapper.CheckerMapper
+import ru.codebattles.backend.entity.Checker
+import ru.codebattles.backend.repository.CheckerRepository
+import ru.codebattles.backend.services.CheckerService
+import ru.codebattles.backend.web.entity.CheckerCreate
+import java.io.IOException
+
+@Tag(name = "Checkers", description = "Endpoints for managing checkers")
+@RestController
+@RequestMapping("/api/checkers")
+@SecurityRequirement(name = "JWT")
+class CheckerController(
+ val checkerRepository: CheckerRepository,
+ val checkerMapper: CheckerMapper,
+ private val checkerService: CheckerService,
+) {
+ @Operation(
+ summary = "[ADMIN] Get all checkers",
+ description = "Retrieves a list of all checkers. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping
+ fun getAll(): List {
+ return checkerMapper.toDtoS(
+ checkerRepository.findAll()
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get checker by ID",
+ description = "Retrieves a checker by its ID. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("/{id}")
+ fun getById(@PathVariable id: Long): CheckerDto {
+ return checkerMapper.toDto(
+ checkerRepository.getById(id)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get checker by ID (extra fields)",
+ description = "Retrieves a checker by its ID with admin-level access. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("/{id}/admin")
+ fun getByIdADMIN(@PathVariable id: Long): Checker {
+ val checkerOptional = checkerRepository.findById(id)
+ return checkerOptional.get()
+ }
+
+ @Operation(
+ summary = "[ADMIN] Update a checker",
+ description = "Applies partial updates to a checker by its ID. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PatchMapping("/{id}")
+ @Throws(IOException::class)
+ fun patch(@PathVariable id: Long, @RequestBody patchNode: JsonNode): Checker {
+ return checkerService.patch(id, patchNode)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Create a checker",
+ description = "Creates a new checker. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping
+ fun create(@RequestBody checkerCreateDto: CheckerCreate): Checker {
+
+ val checker = Checker(
+ checkerCreateDto.displayName,
+ checkerCreateDto.languageHighlightName,
+ checkerCreateDto.address,
+ )
+
+ return checkerService.create(checker)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Delete a checker",
+ description = "Deletes a specific checker by its ID. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @DeleteMapping("/{id}")
+ fun delete(@PathVariable id: Long): Checker? {
+ return checkerService.delete(id)
+ }
+
+
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerSystemEndpointController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerSystemEndpointController.kt
new file mode 100644
index 0000000..6f0fc63
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CheckerSystemEndpointController.kt
@@ -0,0 +1,46 @@
+package ru.codebattles.backend.web.controllers
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+import ru.codebattles.backend.entity.AnswerStatus
+import ru.codebattles.backend.repository.AnswerRepository
+import ru.codebattles.backend.web.entity.checker.CheckerCallback
+
+@Tag(name = "Checker system API", description = "Endpoints for checker system")
+@RestController
+class CheckerSystemEndpointController(
+ val answerRepository: AnswerRepository,
+ val objectMapper: ObjectMapper
+) {
+ @Operation(
+ summary = "(Internal method) Handle checker system callback",
+ description = "Processes the callback from the checker system, updates the answer status, and calculates the score. " +
+ "Used only for checkers. Access disabled if used via gateway"
+ )
+ @PostMapping("/api/check_system_callback")
+ fun checkerCallBack(@RequestBody data: CheckerCallback) {
+ println()
+
+ val answer = answerRepository.getById(data.meta)
+
+ val countOfTests = data.results.size
+ val countOfSuccessTests = data.results.count { it.success }
+
+ var score = 0
+ if (countOfTests > 0) {
+ score = countOfSuccessTests / countOfTests * 100
+ }
+
+ answer.result = objectMapper.writeValueAsString(data)
+ answer.status = AnswerStatus.COMPLETED
+ answer.score = score
+
+
+
+ answerRepository.save(answer)
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsController.kt
new file mode 100644
index 0000000..b53bdc1
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsController.kt
@@ -0,0 +1,180 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import io.swagger.v3.oas.annotations.tags.Tag
+import jakarta.annotation.security.RolesAllowed
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.HttpStatus
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.annotations.CompetitionAccessRequired
+import ru.codebattles.backend.annotations.CompetitionId
+import ru.codebattles.backend.dto.*
+import ru.codebattles.backend.dto.mapper.CompetitionsMapper
+import ru.codebattles.backend.entity.Leaderboard
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.CompetitionRepository
+import ru.codebattles.backend.services.AnswerService
+import ru.codebattles.backend.services.CompetitionService
+import ru.codebattles.backend.web.entity.CompetitionEditDto
+import ru.codebattles.backend.web.entity.EditUsersRequest
+import ru.codebattles.backend.web.entity.SendAnswerRequest
+
+@Tag(name = "Competitions", description = "Endpoints for managing competitions")
+@RestController
+@RequestMapping("/api/competitions")
+@SecurityRequirement(name = "JWT")
+class CompetitionsController (
+ private val competitionMapper: CompetitionsMapper
+) {
+ @Autowired
+ private lateinit var competitionRepository: CompetitionRepository
+
+ @Autowired
+ private lateinit var competitionService: CompetitionService
+
+ @Autowired
+ private lateinit var answerService: AnswerService
+
+ @Operation(
+ summary = "[ADMIN] Create a new competition",
+ description = "Creates a new competition object. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping
+ fun create(@RequestBody instance: CompetitionCreateDto, @AuthenticationPrincipal user: User): CompetitionDto {
+ return competitionService.create(instance, user)
+ }
+
+ @Operation(
+ summary = "Get competition by ID",
+ description = "Retrieves competition details by its ID. Requires access to the competition."
+ )
+ @CompetitionAccessRequired
+ @GetMapping("{compId}")
+ fun getById(@CompetitionId @PathVariable compId: Long): CompetitionDto {
+ return competitionService.getById(compId)
+ }
+
+ @Operation(
+ summary = "Submit an answer",
+ description = "Allows a user to submit an answer for a specific competition problem."
+ )
+ @CompetitionAccessRequired
+ @PostMapping("{compId}/send")
+ fun send(
+ @CompetitionId @PathVariable compId: Long,
+ @AuthenticationPrincipal user: User,
+ @RequestBody data: SendAnswerRequest
+ ): String {
+ answerService.createAnswer(user, data)
+ return "aboba"
+ }
+
+ @Operation(
+ summary = "Get all answers",
+ description = "Retrieves all answers submitted by the authenticated user for a specific competition."
+ )
+ @CompetitionAccessRequired
+ @GetMapping("{compId}/sends")
+ fun getAnswers(
+ @CompetitionId @PathVariable compId: Long,
+ @AuthenticationPrincipal user: User,
+ ): List {
+ return answerService.getAllAnswersByCompetitionsAndUserId(compId, user.id!!)
+ }
+
+ @Operation(
+ summary = "Get competition problems",
+ description = "Retrieves all problems associated with a specific competition."
+ )
+ @CompetitionAccessRequired
+ @GetMapping("{compId}/problems")
+ fun getProblemsByCompetition(@CompetitionId @PathVariable compId: Long): List {
+ return competitionService.getProblemsById(compId)
+ }
+
+ @Operation(
+ summary = "Get competition leaderboard",
+ description = "Retrieves the leaderboard for a specific competition."
+ )
+ @CompetitionAccessRequired
+ @GetMapping("{compId}/leaderboard")
+ fun leaderboard(@CompetitionId @PathVariable compId: Long): Leaderboard {
+ return competitionService.getLeaderboardById(compId)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Edit competition users",
+ description = "Updates the list of users participating in a specific competition. Required admin role."
+ )
+ @PutMapping("{compId}/users")
+ @RolesAllowed("ADMIN")
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ fun editUsers(@PathVariable compId: Long, @RequestBody data: EditUsersRequest) {
+ competitionService.patchUsers(compId, data.usersIds)
+ }
+
+ @RolesAllowed("ADMIN")
+ @PutMapping("{compId}")
+ fun update(@PathVariable compId: Long, @RequestBody profileData: CompetitionEditDto): CompetitionDto {
+ val competition = competitionRepository.getById(compId)
+
+ competitionMapper.update(profileData, competition)
+ competitionRepository.save(competition)
+
+ return competitionMapper.toDto(competition)
+ }
+
+
+ @Operation(
+ summary = "[ADMIN] Edit competition checkers",
+ description = "Updates the list of checkers for a specific competition. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PutMapping("{compId}/checkers")
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ fun editCheckers(@PathVariable compId: Long, @RequestBody data: EditUsersRequest) {
+ competitionService.patchCheckers(compId, data.usersIds)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get competition users",
+ description = "Retrieves the list of users participating in a specific competition. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("{compId}/users")
+ fun getUsers(@PathVariable compId: Long): List {
+ return competitionService.getUsers(compId)
+ }
+
+ @Operation(
+ summary = "Get competition problem by ID",
+ description = "Retrieves a specific problem by its ID within a competition."
+ )
+ @CompetitionAccessRequired
+ @GetMapping("{compId}/problems/{id}")
+ fun getProblemsByIdByCompetition(@CompetitionId @PathVariable compId: Long, @PathVariable id: Long): CompetitionsProblemsDto {
+ return competitionService.getProblemById(compId, id)
+ }
+
+ @Operation(
+ summary = "Get competitions available for user",
+ description = "Retrieves all competitions accessible to the authenticated user."
+ )
+ @GetMapping("/me")
+ fun getAllAvaliableForUser(@AuthenticationPrincipal user: User): List {
+ return competitionService.getAllByUser(user)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get all competitions",
+ description = "Retrieves a list of all competitions. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping
+ fun getAll(): List {
+ return competitionService.getAll()
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsProblemsController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsProblemsController.kt
new file mode 100644
index 0000000..3211a35
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/CompetitionsProblemsController.kt
@@ -0,0 +1,123 @@
+package ru.codebattles.backend.web.controllers
+
+import com.fasterxml.jackson.databind.JsonNode
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import io.swagger.v3.oas.annotations.tags.Tag
+import jakarta.annotation.security.RolesAllowed
+import org.springdoc.core.annotations.ParameterObject
+import org.springframework.data.domain.Page
+import org.springframework.data.domain.Pageable
+import org.springframework.data.web.PagedModel
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.CompetitionsProblemsDto
+import ru.codebattles.backend.dto.mapper.CompetitionsProblemsMapper
+import ru.codebattles.backend.entity.CompetitionsProblems
+import ru.codebattles.backend.services.CompetitionsProblemsService
+import ru.codebattles.backend.web.entity.CreateCompetitionProblem
+import java.io.IOException
+
+@Tag(name = "Competition Problems", description = "Endpoints for managing competition problems")
+@RestController
+@RequestMapping("/api/competitionsProblems")
+@SecurityRequirement(name = "JWT")
+class CompetitionsProblemsController(
+ private val competitionsProblemsService: CompetitionsProblemsService,
+ private val competitionsProblemsMapper: CompetitionsProblemsMapper,
+) {
+ @Operation(
+ summary = "[ADMIN] Get all competition problems",
+ description = "Retrieves a paginated list of all competition problems. Required admin role."
+ )
+ @Deprecated("Dont use global getter")
+ @RolesAllowed("ADMIN")
+ @GetMapping
+ fun getAll(@ParameterObject pageable: Pageable): PagedModel {
+ val competitionsProblems: Page = competitionsProblemsService.getAll(pageable)
+ return PagedModel(competitionsProblems)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get competition problem by ID",
+ description = "Retrieves details of a specific competition problem by its ID. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("/{id}")
+ fun getOne(@PathVariable id: Long): CompetitionsProblemsDto {
+
+ return competitionsProblemsMapper.toDto(
+ competitionsProblemsService.getOne(id)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get multiple competition problems",
+ description = "Retrieves details of multiple competition problems by their IDs. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("/by-ids")
+ fun getMany(@RequestParam ids: List): List {
+ return competitionsProblemsMapper.toDtoS(
+ competitionsProblemsService.getMany(ids)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Create a competition problem",
+ description = "Creates a new competition problem."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping
+ fun create(@RequestBody data: CreateCompetitionProblem): CompetitionsProblemsDto {
+ return competitionsProblemsMapper.toDto(
+ competitionsProblemsService.create(data)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Update a competition problem",
+ description = "Applies partial updates to a specific competition problem. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PatchMapping("/{id}")
+ @Throws(IOException::class)
+ fun patch(@PathVariable id: Long, @RequestBody patchNode: JsonNode): CompetitionsProblemsDto {
+ return competitionsProblemsMapper.toDto(
+ competitionsProblemsService.patch(id, patchNode)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Update multiple competition problems",
+ description = "Applies partial updates to multiple competition problems. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PatchMapping
+ @Throws(IOException::class)
+ fun patchMany(@RequestParam ids: List, @RequestBody patchNode: JsonNode): List {
+ return competitionsProblemsService.patchMany(ids, patchNode)
+ }
+
+
+ @Operation(
+ summary = "[ADMIN] Delete a competition problem",
+ description = "Deletes a specific competition problem by its ID. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @DeleteMapping("/{id}")
+ fun delete(@PathVariable id: Long): CompetitionsProblemsDto {
+ return competitionsProblemsMapper.toDto(
+ competitionsProblemsService.delete(id)!!
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Delete multiple competition problems",
+ description = "Deletes multiple competition problems by their IDs. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @DeleteMapping
+ fun deleteMany(@RequestParam ids: List) {
+ competitionsProblemsService.deleteMany(ids)
+ }
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/GlobalExceptionHandler.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/GlobalExceptionHandler.kt
new file mode 100644
index 0000000..db317f7
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/GlobalExceptionHandler.kt
@@ -0,0 +1,33 @@
+package ru.codebattles.backend.web.controllers
+
+import org.springframework.http.HttpStatus
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.core.AuthenticationException
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseStatus
+import org.springframework.web.bind.annotation.RestControllerAdvice
+import ru.codebattles.backend.web.entity.errors.AccessDeniedResponse
+import ru.codebattles.backend.web.entity.errors.InternalServerErrorResponse
+import ru.codebattles.backend.web.entity.errors.UnauthorizedResponse
+
+
+@RestControllerAdvice
+class GlobalExceptionHandler {
+ @ExceptionHandler(Throwable::class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ fun handleThrowable(e: Throwable): InternalServerErrorResponse {
+ return InternalServerErrorResponse(message = e.message)
+ }
+
+ @ExceptionHandler(AccessDeniedException::class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ fun handleAccessDeniedException(e: AccessDeniedException): AccessDeniedResponse {
+ return AccessDeniedResponse(message = e.message)
+ }
+
+ @ExceptionHandler(AuthenticationException::class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ fun handleAuthenticationException(e: AuthenticationException): UnauthorizedResponse {
+ return UnauthorizedResponse(message = e.message)
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/PingPongController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/PingPongController.kt
new file mode 100644
index 0000000..2653df2
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/PingPongController.kt
@@ -0,0 +1,22 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import ru.codebattles.backend.web.entity.RenderedError
+
+@Tag(name = "Ping Pong", description = "Endpoints for ping-pong testing")
+@RestController
+@RequestMapping("/api/ping")
+class PingPongController {
+ @Operation(
+ summary = "Ping endpoint",
+ description = "Returns a 'pong' response to test the API availability."
+ )
+ @GetMapping
+ fun ping(): RenderedError {
+ return RenderedError(detail = "pong")
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProblemsController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProblemsController.kt
new file mode 100644
index 0000000..02beaf8
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProblemsController.kt
@@ -0,0 +1,82 @@
+package ru.codebattles.backend.web.controllers
+
+import com.fasterxml.jackson.databind.JsonNode
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.tags.Tag
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import jakarta.annotation.security.RolesAllowed
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.CreateProblemDto
+import ru.codebattles.backend.dto.ProblemDto
+import ru.codebattles.backend.entity.Problem
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.ProblemsRepository
+import ru.codebattles.backend.services.ProblemsService
+import java.io.IOException
+
+@Tag(name = "Problems", description = "Endpoints for managing problems")
+@RestController
+@RequestMapping("/api/problems")
+@SecurityRequirement(name = "JWT")
+class ProblemsController {
+
+ @Autowired
+ private lateinit var problemsRepository: ProblemsRepository
+
+ @Autowired
+ private lateinit var problemsService: ProblemsService
+
+ @Operation(
+ summary = "[ADMIN] Create a new problem",
+ description = "Creates a new problem using the provided data."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping
+ fun create(@RequestBody instance: CreateProblemDto, @AuthenticationPrincipal user: User): ProblemDto {
+ return problemsService.create(instance)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get problem by ID",
+ description = "Retrieves a problem by its ID. Required admin role"
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("{id}")
+ fun getById(@PathVariable id: Long): ProblemDto {
+ return problemsService.getById(id)
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get problem by ID (extra fields)",
+ description = "Retrieves a problem by its ID with admin-level access. Required admin role"
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping("{id}/admin")
+ fun getByIdAdmin(@PathVariable id: Long): Problem {
+ val problemOptional = problemsRepository.findById(id)
+ return problemOptional.get()
+ }
+
+ @Operation(
+ summary = "[ADMIN] Get all problems",
+ description = "Retrieves a list of all problems."
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping
+ fun getAll(): Iterable {
+ return problemsService.getAll()
+ }
+
+ @Operation(
+ summary = "[ADMIN] Update a problem",
+ description = "Applies partial updates to a problem by its ID."
+ )
+ @RolesAllowed("ADMIN")
+ @PatchMapping("/{id}")
+ @Throws(IOException::class)
+ fun patch(@PathVariable id: Long, @RequestBody patchNode: JsonNode): Problem {
+ return problemsService.patch(id, patchNode)
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProfileController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProfileController.kt
new file mode 100644
index 0000000..168296e
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/ProfileController.kt
@@ -0,0 +1,37 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.UserProfileDto
+import ru.codebattles.backend.dto.UserProfileEditDto
+import ru.codebattles.backend.dto.mapper.UserMapper
+import ru.codebattles.backend.dto.mapper.UserProfileMapper
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.UserRepository
+
+@Tag(name = "Profile", description = "Endpoints for current profile")
+@RestController
+@RequestMapping("/api/profile")
+@SecurityRequirement(name = "JWT")
+class ProfileController(
+ private val userRepository: UserRepository,
+ private val userMapper: UserMapper,
+ private val userProfileMapper: UserProfileMapper,
+) {
+
+ @PutMapping
+ fun updateProfile(@RequestBody profileData: UserProfileEditDto, @AuthenticationPrincipal user: User): UserProfileDto {
+ userMapper.updateUserProfile(profileData, user)
+ userRepository.save(user)
+
+ return userProfileMapper.toDto(user)
+ }
+
+ @GetMapping
+ fun getMe(@AuthenticationPrincipal user: User): UserProfileDto {
+ return userProfileMapper.toDto(user)
+ }
+
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/UsersController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/UsersController.kt
new file mode 100644
index 0000000..8090dce
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/UsersController.kt
@@ -0,0 +1,81 @@
+package ru.codebattles.backend.web.controllers
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.security.SecurityRequirement
+import io.swagger.v3.oas.annotations.tags.Tag
+import jakarta.annotation.security.RolesAllowed
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.*
+import ru.codebattles.backend.dto.CreateUserDto
+import ru.codebattles.backend.dto.UserDto
+import ru.codebattles.backend.dto.mapper.UserMapper
+import ru.codebattles.backend.entity.User
+import ru.codebattles.backend.repository.UserRepository
+import ru.codebattles.backend.services.CompetitionService
+import ru.codebattles.backend.services.UserService
+import ru.codebattles.backend.web.entity.LinkUserRequest
+import ru.codebattles.backend.web.entity.OkResponse
+import java.util.*
+
+@Tag(name = "Users", description = "Endpoints for managing users")
+@RestController
+@RequestMapping("/api/users")
+@SecurityRequirement(name = "JWT")
+class UsersController(
+ val userRepository: UserRepository,
+ val userMapper: UserMapper,
+ private val userService: UserService,
+ private val competitionService: CompetitionService,
+) {
+ @Operation(
+ summary = "Get current user",
+ description = "Retrieves current user."
+ )
+ @GetMapping("me")
+ fun getProfile(@AuthenticationPrincipal user: User): Optional {
+ return userRepository.findById(user.id!!)
+ }
+
+
+ @Operation(
+ summary = "[ADMIN] Get all users",
+ description = "Retrieves a list of all users. Required admin role"
+ )
+ @RolesAllowed("ADMIN")
+ @GetMapping
+ fun getAll(@AuthenticationPrincipal user: User): List {
+ return userMapper.toDtoS(
+ userRepository.findAll()
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Create user",
+ description = "Create user with provided data. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping
+ fun create(@RequestBody(required = true) userDto: CreateUserDto): UserDto {
+
+ return userMapper.toDto(
+ userService.create(userDto)
+ )
+ }
+
+ @Operation(
+ summary = "[ADMIN] Link user to competition",
+ description = "Links a user to a competition. Required admin role."
+ )
+ @RolesAllowed("ADMIN")
+ @PostMapping("link")
+ fun linkUser(@RequestBody(required = true) linkReq: LinkUserRequest): OkResponse {
+
+ competitionService.joinUser(
+ linkReq.competitionId,
+ linkReq.userId,
+ )
+
+ return OkResponse()
+ }
+
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/libs/SwaggerRedirectController.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/libs/SwaggerRedirectController.kt
new file mode 100644
index 0000000..86b7479
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/libs/SwaggerRedirectController.kt
@@ -0,0 +1,10 @@
+package ru.codebattles.backend.web.controllers.libs
+
+import org.springframework.stereotype.Controller
+import org.springframework.web.bind.annotation.GetMapping
+
+@Controller
+class SwaggerRedirectController {
+ @GetMapping("/swagger-ui")
+ fun redirectToNewUrl() = "redirect:/swagger-ui/index.html"
+}
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CheckerCreate.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CheckerCreate.kt
new file mode 100644
index 0000000..0eba3f9
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CheckerCreate.kt
@@ -0,0 +1,15 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Details for creating a new checker")
+data class CheckerCreate(
+ @Schema(description = "Display name of the checker", example = "Python Checker")
+ val displayName: String,
+
+ @Schema(description = "Language highlight name for the checker", example = "python")
+ val languageHighlightName: String,
+
+ @Schema(description = "Address of the checker service", example = "http://localhost:8080")
+ val address: String,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CompetitionEditDto.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CompetitionEditDto.kt
new file mode 100644
index 0000000..f9ee869
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CompetitionEditDto.kt
@@ -0,0 +1,19 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+import java.time.LocalDateTime
+
+data class CompetitionEditDto(
+
+ @Schema(description = "Name of the competition", example = "Code Battles 2023")
+ val name: String,
+
+ @Schema(description = "Description of the competition", example = "A competitive coding event")
+ val description: String,
+
+ @Schema(description = "Start time of the competition", example = "2023-01-01T10:00:00")
+ val startedAt: LocalDateTime? = null,
+
+ @Schema(description = "End time of the competition", example = "2023-01-01T18:00:00")
+ val endedAt: LocalDateTime? = null
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CreateCompetitionProblem.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CreateCompetitionProblem.kt
new file mode 100644
index 0000000..d3aa1d4
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/CreateCompetitionProblem.kt
@@ -0,0 +1,18 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Details for creating a competition problem")
+class CreateCompetitionProblem(
+ @Schema(description = "Priority of the problem in the competition", example = "1")
+ val priority: Long,
+
+ @Schema(description = "Slug identifier for the problem", example = "problem-slug")
+ val slug: String,
+
+ @Schema(description = "ID of the competition", example = "1001")
+ val competition_id: Long,
+
+ @Schema(description = "ID of the problem", example = "2002")
+ val problem_id: Long,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/EditUsersRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/EditUsersRequest.kt
new file mode 100644
index 0000000..47dbafc
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/EditUsersRequest.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Request to edit users")
+data class EditUsersRequest(
+ @Schema(description = "Set of user IDs to be edited", example = "1,2,3")
+ val usersIds: Set
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/LinkUserRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/LinkUserRequest.kt
new file mode 100644
index 0000000..fe69c94
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/LinkUserRequest.kt
@@ -0,0 +1,12 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Request to link a user to a competition")
+data class LinkUserRequest(
+ @Schema(description = "ID of the user", example = "1")
+ val userId: Long,
+
+ @Schema(description = "ID of the competition", example = "1001")
+ val competitionId: Long,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/OkResponse.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/OkResponse.kt
new file mode 100644
index 0000000..881925c
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/OkResponse.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Response indicating success")
+data class OkResponse(
+ @Schema(description = "Status of the response", example = "OK")
+ val status: String = "OK",
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/ProblemCreateRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/ProblemCreateRequest.kt
new file mode 100644
index 0000000..0af68c1
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/ProblemCreateRequest.kt
@@ -0,0 +1,27 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Request to create a problem")
+data class ProblemCreateRequest(
+ @Schema(description = "Name of the problem", example = "Sum of Two Numbers")
+ val name: String,
+
+ @Schema(description = "Description of the problem", example = "Calculate the sum of two integers.")
+ val description: String,
+
+ @Schema(description = "Input data for the problem", example = "2 3")
+ val inData: String,
+
+ @Schema(description = "Expected output data for the problem", example = "5")
+ val outData: String,
+
+ @Schema(description = "Test cases for the problem", example = "[{\"in\": \"2 3\", \"out\": \"5\"}]")
+ val tests: String,
+
+ @Schema(description = "Example cases for the problem", example = "[{\"in\": \"1 1\", \"out\": \"2\"}]")
+ val examples: String,
+
+ @Schema(description = "Whether the problem is public", example = "true")
+ val public: Boolean? = false
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/RenderedError.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/RenderedError.kt
new file mode 100644
index 0000000..b5d42bf
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/RenderedError.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Details of an error response")
+data class RenderedError(
+ @Schema(description = "Detailed error message", example = "Invalid input data")
+ val detail: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/SendAnswerRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/SendAnswerRequest.kt
new file mode 100644
index 0000000..9444f7e
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/SendAnswerRequest.kt
@@ -0,0 +1,15 @@
+package ru.codebattles.backend.web.entity
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Request to send an answer")
+data class SendAnswerRequest(
+ @Schema(description = "ID of the checker", example = "1")
+ val checker: Long,
+
+ @Schema(description = "Source code submitted as the answer", example = "print(input())")
+ val src: String,
+
+ @Schema(description = "ID of the answer", example = "1001")
+ val id: Long,
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthRequest.kt
new file mode 100644
index 0000000..1802e37
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthRequest.kt
@@ -0,0 +1,11 @@
+package ru.codebattles.backend.web.entity.auth
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Request for user authentication")
+data class AuthRequest(
+ @Schema(description = "Username of the user", example = "john_doe")
+ val username: String,
+
+ @Schema(description = "Password of the user", example = "password123")
+ val password: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthResponse.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthResponse.kt
new file mode 100644
index 0000000..a227331
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/auth/AuthResponse.kt
@@ -0,0 +1,9 @@
+package ru.codebattles.backend.web.entity.auth
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Response containing authentication token")
+data class AuthResponse(
+ @Schema(description = "JWT token for authentication", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
+ val token: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerCallback.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerCallback.kt
new file mode 100644
index 0000000..e0d2e45
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerCallback.kt
@@ -0,0 +1,13 @@
+package ru.codebattles.backend.web.entity.checker
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Callback response from the checker")
+@JvmRecord
+data class CheckerCallback(
+ @Schema(description = "List of program results")
+ val results: List,
+
+ @Schema(description = "Metadata associated with the callback", example = "12345")
+ val meta: Long
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerTaskRequest.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerTaskRequest.kt
new file mode 100644
index 0000000..76c1c3d
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/CheckerTaskRequest.kt
@@ -0,0 +1,27 @@
+package ru.codebattles.backend.web.entity.checker
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Test case for the checker")
+data class Test(
+ @Schema(description = "Input for the test case", example = "2 3")
+ val `in`: String,
+
+ @Schema(description = "Expected output for the test case", example = "5")
+ val out: String,
+)
+
+@Schema(description = "Request to create a checker task")
+data class CheckerTaskRequest(
+ @Schema(description = "Source code to be checked", example = "print(input())")
+ val source: String,
+
+ @Schema(description = "Compiler to be used", example = "python3")
+ val compiler: String,
+
+ @Schema(description = "List of test cases")
+ val tests: List,
+
+ @Schema(description = "Metadata for the task", example = "task-123")
+ val meta: String
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProcessEndStatus.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProcessEndStatus.kt
new file mode 100644
index 0000000..555f1cd
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProcessEndStatus.kt
@@ -0,0 +1,18 @@
+package ru.codebattles.backend.web.entity.checker
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Status of the process execution")
+enum class ProcessEndStatus(private val msg: String) {
+ SUCCESS("OK"),
+ RUNTIME_ERROR("RE"),
+ COMPILE_ERROR("CE"),
+ TIME_LIMIT("TL"),
+ WRONG_ANSWER("WA"),
+ NOT_EXECUTED("NE"),
+ ;
+
+ override fun toString(): String {
+ return msg
+ }
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProgramResult.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProgramResult.kt
new file mode 100644
index 0000000..45327ff
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/checker/ProgramResult.kt
@@ -0,0 +1,19 @@
+package ru.codebattles.backend.web.entity.checker
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(description = "Result of a program execution")
+@JvmRecord
+data class ProgramResult(
+ @Schema(description = "Indicates if the execution was successful", example = "true")
+ val success: Boolean,
+
+ @Schema(description = "Output of the program", example = "Hello, World!")
+ val out: String,
+
+ @Schema(description = "Status message of the execution", example = "OK")
+ val msg: ProcessEndStatus,
+
+ @Schema(description = "Execution time in milliseconds", example = "150")
+ val time: Int
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/AccessDeniedResponse.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/AccessDeniedResponse.kt
new file mode 100644
index 0000000..c4c35fe
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/AccessDeniedResponse.kt
@@ -0,0 +1,20 @@
+package ru.codebattles.backend.web.entity.errors
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(
+ description = "Response returned when the user does not have permission to access the requested resource."
+)
+class AccessDeniedResponse(
+ @Schema(
+ description = "Detailed message about the error.",
+ example = "You do not have permission to access this resource."
+ )
+ val message: String? = "Forbidden",
+
+ @Schema(
+ description = "HTTP status code representing the error.",
+ example = "403"
+ )
+ val code: Int? = 403
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/InternalServerErrorResponse.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/InternalServerErrorResponse.kt
new file mode 100644
index 0000000..e91e444
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/InternalServerErrorResponse.kt
@@ -0,0 +1,21 @@
+package ru.codebattles.backend.web.entity.errors
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+
+@Schema(
+ description = "Response returned when an unexpected error occurs on the server."
+)
+class InternalServerErrorResponse(
+ @Schema(
+ description = "Detailed message about the error.",
+ example = "An unexpected error occurred. Please try again later."
+ )
+ val message: String? = "Internal Server Error",
+
+ @Schema(
+ description = "HTTP status code representing the error.",
+ example = "500"
+ )
+ val code: Int? = 500
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/UnauthorizedResponse.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/UnauthorizedResponse.kt
new file mode 100644
index 0000000..46a94c3
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/entity/errors/UnauthorizedResponse.kt
@@ -0,0 +1,20 @@
+package ru.codebattles.backend.web.entity.errors
+
+import io.swagger.v3.oas.annotations.media.Schema
+
+@Schema(
+ description = "Response returned when the user is not authenticated."
+)
+class UnauthorizedResponse(
+ @Schema(
+ description = "Detailed message about the error.",
+ example = "Authentication is required to access this resource."
+ )
+ val message: String? = "Unauthorized",
+
+ @Schema(
+ description = "HTTP status code representing the error.",
+ example = "401"
+ )
+ val code: Int? = 401
+)
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/kotlin/ru/codebattles/tools/RandomStringGenerator.kt b/BACKEND_V2/src/main/kotlin/ru/codebattles/tools/RandomStringGenerator.kt
new file mode 100644
index 0000000..407134b
--- /dev/null
+++ b/BACKEND_V2/src/main/kotlin/ru/codebattles/tools/RandomStringGenerator.kt
@@ -0,0 +1,11 @@
+package ru.codebattles.tools
+
+import java.security.SecureRandom
+
+fun generateSecureRandomString(length: Int): String {
+ val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+ val secureRandom = SecureRandom()
+ return (1..length)
+ .map { chars[secureRandom.nextInt(chars.length)] }
+ .joinToString("")
+}
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/resources/application.properties b/BACKEND_V2/src/main/resources/application.properties
new file mode 100644
index 0000000..8ccc9ad
--- /dev/null
+++ b/BACKEND_V2/src/main/resources/application.properties
@@ -0,0 +1,11 @@
+spring.application.name=BACKEND_V2
+spring.datasource.url=jdbc:postgresql://localhost:5432/codebattles
+spring.datasource.username=postgres
+spring.datasource.password=admin
+spring.jpa.hibernate.ddl-auto=none
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+
+#codebattles.jwt.secret-key=alexalexalexalexalexalexalexalexalexalexalexalexalexalexalexalexalexalexalexalex
+
+spring.flyway.enabled=true
+spring.flyway.locations=classpath:db/migrations
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/resources/db/migrations/V1__init.sql b/BACKEND_V2/src/main/resources/db/migrations/V1__init.sql
new file mode 100644
index 0000000..513a2f4
--- /dev/null
+++ b/BACKEND_V2/src/main/resources/db/migrations/V1__init.sql
@@ -0,0 +1,134 @@
+CREATE TABLE answer
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE,
+ updated_at TIMESTAMP WITHOUT TIME ZONE,
+ competition_id BIGINT,
+ user_id BIGINT,
+ status VARCHAR(255),
+ score INTEGER,
+ code VARCHAR(255),
+ checker_id BIGINT,
+ result VARCHAR(255),
+ competitions_problems_id BIGINT,
+ CONSTRAINT pk_answer PRIMARY KEY (id)
+);
+
+CREATE TABLE checker
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE,
+ updated_at TIMESTAMP WITHOUT TIME ZONE,
+ display_name VARCHAR(255),
+ language_highlight_name VARCHAR(255),
+ address VARCHAR(255),
+ CONSTRAINT pk_checker PRIMARY KEY (id)
+);
+
+CREATE TABLE competitions
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE,
+ updated_at TIMESTAMP WITHOUT TIME ZONE,
+ organizer_id BIGINT,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(1000) NOT NULL,
+ started_at TIMESTAMP WITHOUT TIME ZONE,
+ ended_at TIMESTAMP WITHOUT TIME ZONE,
+ show_rating BOOLEAN NOT NULL,
+ CONSTRAINT pk_competitions PRIMARY KEY (id)
+);
+
+CREATE TABLE competitions_checkers
+(
+ competition_id BIGINT NOT NULL,
+ checkers_id BIGINT NOT NULL,
+ CONSTRAINT pk_competitions_checkers PRIMARY KEY (competition_id, checkers_id)
+);
+
+CREATE TABLE competitions_members
+(
+ competition_id BIGINT NOT NULL,
+ members_id BIGINT NOT NULL,
+ CONSTRAINT pk_competitions_members PRIMARY KEY (competition_id, members_id)
+);
+
+CREATE TABLE competitions_problems
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE,
+ updated_at TIMESTAMP WITHOUT TIME ZONE,
+ priority INTEGER NOT NULL,
+ slug VARCHAR(255),
+ competition_id BIGINT,
+ problem_id BIGINT,
+ CONSTRAINT pk_competitionsproblems PRIMARY KEY (id)
+);
+
+CREATE TABLE problem
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ name VARCHAR(255),
+ description TEXT,
+ in_data TEXT,
+ out_data TEXT,
+ tests TEXT,
+ examples TEXT,
+ CONSTRAINT pk_problem PRIMARY KEY (id)
+);
+
+CREATE TABLE users_
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE,
+ updated_at TIMESTAMP WITHOUT TIME ZONE,
+ username VARCHAR(255),
+ password VARCHAR(255),
+ name VARCHAR(255),
+ roles VARCHAR(255),
+ CONSTRAINT pk_users_ PRIMARY KEY (id)
+);
+
+CREATE TABLE variables
+(
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ value VARCHAR(255),
+ CONSTRAINT pk_variables PRIMARY KEY (id)
+);
+
+ALTER TABLE variables
+ ADD CONSTRAINT uc_variables_name UNIQUE (name);
+
+ALTER TABLE answer
+ ADD CONSTRAINT FK_ANSWER_ON_CHECKER FOREIGN KEY (checker_id) REFERENCES checker (id);
+
+ALTER TABLE answer
+ ADD CONSTRAINT FK_ANSWER_ON_COMPETITION FOREIGN KEY (competition_id) REFERENCES competitions (id);
+
+ALTER TABLE answer
+ ADD CONSTRAINT FK_ANSWER_ON_COMPETITIONSPROBLEMS FOREIGN KEY (competitions_problems_id) REFERENCES competitions_problems (id);
+
+ALTER TABLE answer
+ ADD CONSTRAINT FK_ANSWER_ON_USER FOREIGN KEY (user_id) REFERENCES users_ (id);
+
+ALTER TABLE competitions_problems
+ ADD CONSTRAINT FK_COMPETITIONSPROBLEMS_ON_COMPETITION FOREIGN KEY (competition_id) REFERENCES competitions (id);
+
+ALTER TABLE competitions_problems
+ ADD CONSTRAINT FK_COMPETITIONSPROBLEMS_ON_PROBLEM FOREIGN KEY (problem_id) REFERENCES problem (id);
+
+ALTER TABLE competitions
+ ADD CONSTRAINT FK_COMPETITIONS_ON_ORGANIZER FOREIGN KEY (organizer_id) REFERENCES users_ (id);
+
+ALTER TABLE competitions_checkers
+ ADD CONSTRAINT fk_comche_on_checker FOREIGN KEY (checkers_id) REFERENCES checker (id);
+
+ALTER TABLE competitions_checkers
+ ADD CONSTRAINT fk_comche_on_competition FOREIGN KEY (competition_id) REFERENCES competitions (id);
+
+ALTER TABLE competitions_members
+ ADD CONSTRAINT fk_commem_on_competition FOREIGN KEY (competition_id) REFERENCES competitions (id);
+
+ALTER TABLE competitions_members
+ ADD CONSTRAINT fk_commem_on_user FOREIGN KEY (members_id) REFERENCES users_ (id);
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/resources/db/migrations/V2__add_competitions_flags.sql b/BACKEND_V2/src/main/resources/db/migrations/V2__add_competitions_flags.sql
new file mode 100644
index 0000000..45c46f3
--- /dev/null
+++ b/BACKEND_V2/src/main/resources/db/migrations/V2__add_competitions_flags.sql
@@ -0,0 +1,11 @@
+ALTER TABLE competitions
+ ADD show_input BOOLEAN;
+
+ALTER TABLE competitions
+ ADD show_output BOOLEAN;
+
+ALTER TABLE competitions
+ ALTER COLUMN show_input SET NOT NULL;
+
+ALTER TABLE competitions
+ ALTER COLUMN show_output SET NOT NULL;
\ No newline at end of file
diff --git a/BACKEND_V2/src/main/resources/db/migrations/V3__users_role_as_table.sql b/BACKEND_V2/src/main/resources/db/migrations/V3__users_role_as_table.sql
new file mode 100644
index 0000000..304ff2b
--- /dev/null
+++ b/BACKEND_V2/src/main/resources/db/migrations/V3__users_role_as_table.sql
@@ -0,0 +1,11 @@
+CREATE TABLE users_roles
+(
+ user_id BIGINT NOT NULL,
+ role VARCHAR(255)
+);
+
+ALTER TABLE users_roles
+ ADD CONSTRAINT fk_users_roles_on_user FOREIGN KEY (user_id) REFERENCES users_ (id);
+
+ALTER TABLE users_
+ DROP COLUMN roles;
\ No newline at end of file
diff --git a/BACKEND_V2/src/test/kotlin/ru/codebattles/backend/BackendV2ApplicationTests.kt b/BACKEND_V2/src/test/kotlin/ru/codebattles/backend/BackendV2ApplicationTests.kt
new file mode 100644
index 0000000..d39d41d
--- /dev/null
+++ b/BACKEND_V2/src/test/kotlin/ru/codebattles/backend/BackendV2ApplicationTests.kt
@@ -0,0 +1,13 @@
+package ru.codebattles.backend
+
+import org.junit.jupiter.api.Test
+import org.springframework.boot.test.context.SpringBootTest
+
+@SpringBootTest
+class BackendV2ApplicationTests {
+
+ @Test
+ fun contextLoads() {
+ }
+
+}
diff --git a/FRONTEND/src/pages/teacher/LoginPage.jsx b/FRONTEND/src/pages/teacher/LoginPage.jsx
index 5d54ae1..57ce38e 100644
--- a/FRONTEND/src/pages/teacher/LoginPage.jsx
+++ b/FRONTEND/src/pages/teacher/LoginPage.jsx
@@ -7,35 +7,35 @@ import If from "../../components/If";
const LoginPage = () => {
const [login, setLogin] = useState();
const [passsword, setPasssword] = useState();
- const [captchaUserInput, setCaptchaUserInput] = useState("");
+ // const [captchaUserInput, setCaptchaUserInput] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [isLoading, setIsLoading] = useState(false);
- const [captcha, setCaptcha] = useState({})
+ // const [captcha, setCaptcha] = useState({})
const navigate = useNavigate();
- useEffect(() => {
- apiAxios.get(getApiAddress() + '/api/teacher/auth')
- .then((res) => {
- setCaptcha(res.data)
- })
- .catch(() => setErrorMsg("Неверные данные"))
- .finally(() => setIsLoading(false));
-
- }, [errorMsg]);
+ // useEffect(() => {
+ // apiAxios.get(getApiAddress() + '/api/teacher/auth')
+ // .then((res) => {
+ // setCaptcha(res.data)
+ // })
+ // .catch(() => setErrorMsg("Неверные данные"))
+ // .finally(() => setIsLoading(false));
+ //
+ // }, [errorMsg]);
const onSend = async () => {
setIsLoading(true)
- await apiAxios.post(getApiAddress() + '/api/teacher/auth',
+ await apiAxios.post(getApiAddress() + '/api/auth/login',
{
login: login || "",
password: passsword || "",
- base64image: captcha.base64string,
- captchaValidate: captcha.validate,
- captchaUserInput: captchaUserInput.trim(),
+ // base64image: captcha.base64string,
+ // captchaValidate: captcha.validate,
+ // captchaUserInput: captchaUserInput.trim(),
}
)
.then(
@@ -80,12 +80,12 @@ const LoginPage = () => {
placeholder="Введите пароль" onChange={
(e) => setPasssword(e.target.value)
}/>
-
- setCaptchaUserInput(e.target.value)
- }/>
+ {/*
*/}
+ {/* setCaptchaUserInput(e.target.value)*/}
+ {/*}/>*/}
diff --git a/FRONTEND_V2/.eslintrc.cjs b/FRONTEND_V2/.eslintrc.cjs
deleted file mode 100644
index 6df9d7e..0000000
--- a/FRONTEND_V2/.eslintrc.cjs
+++ /dev/null
@@ -1,20 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:react/recommended',
- 'plugin:react/jsx-runtime',
- ],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
- settings: { react: { version: '18.2' } },
- plugins: ['react-refresh'],
- rules: {
- 'react/jsx-no-target-blank': 'off',
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
-}
diff --git a/FRONTEND_V2/Dockerfile b/FRONTEND_V2/Dockerfile
index 6ee9f3c..8214f8b 100644
--- a/FRONTEND_V2/Dockerfile
+++ b/FRONTEND_V2/Dockerfile
@@ -7,7 +7,8 @@ COPY package.json /app
RUN yarn install
COPY . /app
-RUN yarn lint
+# Disabled lint while developing
+# RUN yarn lint
RUN yarn build
# production environment
diff --git a/FRONTEND_V2/eslint.config.js b/FRONTEND_V2/eslint.config.js
new file mode 100644
index 0000000..c481e5b
--- /dev/null
+++ b/FRONTEND_V2/eslint.config.js
@@ -0,0 +1,20 @@
+import globals from "globals";
+import pluginJs from "@eslint/js";
+import pluginReact from "eslint-plugin-react";
+
+/** @type {import('eslint').Linter.Config[]} */
+export default [
+ {
+ files: ["src/**/*.{js,mjs,cjs,jsx}"],
+ languageOptions: { globals: globals.browser }
+ },
+ pluginJs.configs.recommended,
+ pluginReact.configs.flat.recommended,
+ {
+ files: ["src/**/*.{js,mjs,cjs,jsx}"],
+ rules: {
+ "react/react-in-jsx-scope": "off",
+ "no-irregular-whitespace": "warn",
+ }
+ }
+];
\ No newline at end of file
diff --git a/FRONTEND_V2/package-lock.json b/FRONTEND_V2/package-lock.json
index a006e58..91388ab 100644
--- a/FRONTEND_V2/package-lock.json
+++ b/FRONTEND_V2/package-lock.json
@@ -14,6 +14,7 @@
"bootstrap": "5.3.3",
"bootstrap-icons": "^1.11.3",
"dompurify": "^3.1.3",
+ "rc-select": "^14.16.6",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0",
@@ -1089,6 +1090,43 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@rc-component/portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+ "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz",
+ "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/portal": "^1.1.0",
+ "classnames": "^2.3.2",
+ "rc-motion": "^2.0.0",
+ "rc-resize-observer": "^1.3.1",
+ "rc-util": "^5.44.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
"node_modules/@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
@@ -1390,9 +1428,9 @@
}
},
"node_modules/@types/ms": {
- "version": "0.7.34",
- "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
- "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
@@ -1420,6 +1458,12 @@
"@types/react": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "optional": true
+ },
"node_modules/@types/unist": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
@@ -1922,6 +1966,11 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2179,9 +2228,12 @@
}
},
"node_modules/dompurify": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
- "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
+ "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
},
"node_modules/electron-to-chromium": {
"version": "1.4.719",
@@ -3104,15 +3156,15 @@
}
},
"node_modules/hast-util-from-parse5": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz",
- "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+ "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"devlop": "^1.0.0",
- "hastscript": "^8.0.0",
- "property-information": "^6.0.0",
+ "hastscript": "^9.0.0",
+ "property-information": "^7.0.0",
"vfile": "^6.0.0",
"vfile-location": "^5.0.0",
"web-namespaces": "^2.0.0"
@@ -3157,14 +3209,14 @@
}
},
"node_modules/hast-util-from-parse5/node_modules/hastscript": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz",
- "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
+ "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
"dependencies": {
"@types/hast": "^3.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-parse-selector": "^4.0.0",
- "property-information": "^6.0.0",
+ "property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0"
},
"funding": {
@@ -3173,9 +3225,9 @@
}
},
"node_modules/hast-util-from-parse5/node_modules/property-information": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
- "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz",
+ "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
@@ -3199,10 +3251,10 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hast-util-raw": {
- "version": "9.0.4",
- "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
- "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
+"node_modules/hast-util-raw": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
+ "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
@@ -3237,9 +3289,9 @@
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
"node_modules/hast-util-to-jsx-runtime": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
- "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==",
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.5.tgz",
+ "integrity": "sha512-gHD+HoFxOMmmXLuq9f2dZDMQHVcplCVpMfBNRpJsF03yyLZvJGzsFORe8orVuYDX9k2w0VH0uF8oryFd1whqKQ==",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/hast": "^3.0.0",
@@ -3251,7 +3303,7 @@
"mdast-util-mdx-expression": "^2.0.0",
"mdast-util-mdx-jsx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0",
- "property-information": "^6.0.0",
+ "property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"style-to-object": "^1.0.0",
"unist-util-position": "^5.0.0",
@@ -3285,9 +3337,9 @@
}
},
"node_modules/hast-util-to-jsx-runtime/node_modules/property-information": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
- "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz",
+ "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
@@ -3380,413 +3432,412 @@
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"dependencies": {
- "@types/hast": "^2.0.0",
- "comma-separated-tokens": "^1.0.0",
- "hast-util-parse-selector": "^2.0.0",
- "property-information": "^5.0.0",
- "space-separated-tokens": "^1.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/highlight.js": {
- "version": "10.7.3",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
- "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
- "engines": {
- "node": "*"
+ "@types/unist": "*"
}
},
- "node_modules/html-url-attributes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz",
- "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
+ "node_modules/hast-util-from-parse5/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
- "node_modules/html-void-elements": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
- "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "node_modules/hast-util-from-parse5/node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/ignore": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
- "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
- "dev": true,
- "license": "MIT",
- "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,
- "license": "MIT",
+ "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
"dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
+ "@types/hast": "^3.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-meta-resolve": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
- "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
- "dev": true,
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/inline-style-parser": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
- "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g=="
- },
- "node_modules/internal-slot": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
- "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-from-parse5/node_modules/hastscript": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz",
+ "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==",
"dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.0",
- "side-channel": "^1.0.4"
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
},
- "engines": {
- "node": ">= 0.4"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-alphabetical": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
- "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "node_modules/hast-util-from-parse5/node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-alphanumerical": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
- "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
- "dependencies": {
- "is-alphabetical": "^1.0.0",
- "is-decimal": "^1.0.0"
- },
+ "node_modules/hast-util-from-parse5/node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-array-buffer": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
- "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
+ "node_modules/hast-util-parse-selector": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
+ "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-async-function": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
- "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-raw": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
+ "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
"dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "hast-util-to-parse5": "^8.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "parse5": "^7.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-bigint": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
- "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-raw/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dependencies": {
- "has-bigints": "^1.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "@types/unist": "*"
}
},
- "node_modules/is-boolean-object": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
- "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-raw/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
+ "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==",
"dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-object": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
+ "node_modules/hast-util-to-jsx-runtime/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/hast-util-to-jsx-runtime/node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-core-module": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
- "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.0"
- },
+ "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-data-view": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
- "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-to-jsx-runtime/node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hast-util-to-parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
+ "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
"dependencies": {
- "is-typed-array": "^1.1.13"
- },
- "engines": {
- "node": ">= 0.4"
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-date-object": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
- "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-to-parse5/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/hast-util-to-parse5/node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-decimal": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
- "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "node_modules/hast-util-to-parse5/node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "node_modules/hast-util-to-parse5/node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-finalizationregistry": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
- "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
"dependencies": {
- "call-bind": "^1.0.2"
+ "@types/hast": "^3.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/is-generator-function": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
- "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hast-util-whitespace/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "@types/unist": "*"
}
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
+ "node_modules/hastscript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
+ "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"dependencies": {
- "is-extglob": "^2.1.1"
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^1.0.0",
+ "hast-util-parse-selector": "^2.0.0",
+ "property-information": "^5.0.0",
+ "space-separated-tokens": "^1.0.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"engines": {
- "node": ">=0.10.0"
+ "node": "*"
}
},
- "node_modules/is-hexadecimal": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
- "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">= 4"
}
},
- "node_modules/is-negative-zero": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
- "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
- "dev": true,
+ "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,
"license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
"engines": {
- "node": ">= 0.4"
+ "node": ">=6"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-number-object": {
+ "node_modules/import-meta-resolve": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
+ "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
+ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="
+ },
+ "node_modules/internal-slot": {
"version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "has-tostringtag": "^1.0.0"
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.0",
+ "side-channel": "^1.0.4"
},
"engines": {
"node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-plain-obj": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
- "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
- "engines": {
- "node": ">=12"
+ "node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "node_modules/is-array-buffer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
+ "get-intrinsic": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
@@ -3795,12 +3846,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
"engines": {
"node": ">= 0.4"
},
@@ -3808,29 +3862,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7"
- },
- "engines": {
- "node": ">= 0.4"
+ "has-bigints": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-string": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
- "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dev": true,
"license": "MIT",
"dependencies": {
+ "call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
@@ -3840,15 +3892,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-symbol": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
- "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.2"
- },
"engines": {
"node": ">= 0.4"
},
@@ -3856,14 +3905,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-typed-array": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
- "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "which-typed-array": "^1.1.14"
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
+ "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -3872,12 +3934,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
"engines": {
"node": ">= 0.4"
},
@@ -3885,10 +3950,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-weakref": {
+ "node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
- "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3898,15 +3982,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-weakset": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
- "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
- "get-intrinsic": "^1.2.4"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -3915,38 +3998,260 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/iterator.prototype": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
- "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "define-properties": "^1.2.1",
- "get-intrinsic": "^1.2.1",
- "has-symbols": "^1.0.3",
- "reflect.getprototypeof": "^1.0.4",
- "set-function-name": "^2.0.1"
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+ "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -4102,60 +4407,977 @@
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lowlight": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
+ "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
+ "dependencies": {
+ "fault": "^1.0.0",
+ "highlight.js": "~10.7.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.8",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
+ "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
+ "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
+ "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-from-markdown/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
+ "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
+ "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
+ "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz",
+ "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast/node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
+ "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
+ "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
+ "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
+ "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
+ "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
+ "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
+ "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
+ "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
+ "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
+ "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
+ "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
+ "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
+ "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
+ "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
+ "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
+ "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
+ "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
+ "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
}
},
- "node_modules/lowlight": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
- "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
+ "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"dependencies": {
- "fault": "^1.0.0",
- "highlight.js": "~10.7.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "micromark-util-types": "^2.0.0"
}
},
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz",
+ "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"dependencies": {
- "yallist": "^3.0.2"
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
}
},
- "node_modules/magic-string": {
- "version": "0.30.8",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
- "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
- "dev": true,
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
+ "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- },
- "engines": {
- "node": ">=12"
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
}
},
- "node_modules/markdown-table": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
- "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+"node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/mdast-util-find-and-replace": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
- "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
"dependencies": {
"@types/mdast": "^4.0.0",
"escape-string-regexp": "^5.0.0",
@@ -4179,9 +5401,9 @@
}
},
"node_modules/mdast-util-from-markdown": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
- "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
"dependencies": {
"@types/mdast": "^4.0.0",
"@types/unist": "^3.0.0",
@@ -4207,9 +5429,9 @@
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
"node_modules/mdast-util-gfm": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
- "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
"dependencies": {
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-gfm-autolink-literal": "^2.0.0",
@@ -4241,9 +5463,9 @@
}
},
"node_modules/mdast-util-gfm-footnote": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
- "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.1.0",
@@ -4302,9 +5524,9 @@
}
},
"node_modules/mdast-util-mdx-expression": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
- "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
@@ -4327,9 +5549,9 @@
}
},
"node_modules/mdast-util-mdx-jsx": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz",
- "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
@@ -4362,15 +5584,6 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
- "node_modules/mdast-util-mdx-jsx/node_modules/character-entities": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
- "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/mdast-util-mdx-jsx/node_modules/character-entities-legacy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
@@ -4430,12 +5643,11 @@
}
},
"node_modules/mdast-util-mdx-jsx/node_modules/parse-entities": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
- "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
"dependencies": {
"@types/unist": "^2.0.0",
- "character-entities": "^2.0.0",
"character-entities-legacy": "^3.0.0",
"character-reference-invalid": "^2.0.0",
"decode-named-character-reference": "^1.0.0",
@@ -4520,15 +5732,16 @@
}
},
"node_modules/mdast-util-to-markdown": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
- "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
"dependencies": {
"@types/mdast": "^4.0.0",
"@types/unist": "^3.0.0",
"longest-streak": "^3.0.0",
"mdast-util-phrasing": "^4.0.0",
"mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
"micromark-util-decode-string": "^2.0.0",
"unist-util-visit": "^5.0.0",
"zwitch": "^2.0.0"
@@ -4556,9 +5769,9 @@
}
},
"node_modules/micromark": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
- "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4590,9 +5803,9 @@
}
},
"node_modules/micromark-core-commonmark": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
- "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4693,9 +5906,9 @@
}
},
"node_modules/micromark-extension-gfm-table": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
- "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
"dependencies": {
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
@@ -4737,9 +5950,9 @@
}
},
"node_modules/micromark-factory-destination": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
- "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4757,9 +5970,9 @@
}
},
"node_modules/micromark-factory-label": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
- "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4778,9 +5991,9 @@
}
},
"node_modules/micromark-factory-space": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
- "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4797,9 +6010,9 @@
}
},
"node_modules/micromark-factory-title": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
- "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4818,9 +6031,9 @@
}
},
"node_modules/micromark-factory-whitespace": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
- "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4839,9 +6052,9 @@
}
},
"node_modules/micromark-util-character": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
- "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4858,9 +6071,9 @@
}
},
"node_modules/micromark-util-chunked": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
- "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4876,9 +6089,9 @@
}
},
"node_modules/micromark-util-classify-character": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
- "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4896,9 +6109,9 @@
}
},
"node_modules/micromark-util-combine-extensions": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
- "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4915,9 +6128,9 @@
}
},
"node_modules/micromark-util-decode-numeric-character-reference": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
- "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4933,9 +6146,9 @@
}
},
"node_modules/micromark-util-decode-string": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
- "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4954,9 +6167,9 @@
}
},
"node_modules/micromark-util-encode": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
- "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4969,9 +6182,9 @@
]
},
"node_modules/micromark-util-html-tag-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
- "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -4984,9 +6197,9 @@
]
},
"node_modules/micromark-util-normalize-identifier": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
- "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5002,9 +6215,9 @@
}
},
"node_modules/micromark-util-resolve-all": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
- "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5020,9 +6233,9 @@
}
},
"node_modules/micromark-util-sanitize-uri": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz",
- "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5040,9 +6253,9 @@
}
},
"node_modules/micromark-util-subtokenize": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
- "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5061,9 +6274,9 @@
}
},
"node_modules/micromark-util-symbol": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz",
- "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5076,9 +6289,9 @@
]
},
"node_modules/micromark-util-types": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz",
- "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
"funding": [
{
"type": "GitHub Sponsors",
@@ -5369,11 +6582,11 @@
}
},
"node_modules/parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
- "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
"dependencies": {
- "entities": "^4.4.0"
+ "entities": "^4.5.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
@@ -5537,6 +6750,107 @@
],
"license": "MIT"
},
+ "node_modules/rc-motion": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
+ "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-overflow": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz",
+ "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.37.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-resize-observer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
+ "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.7",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.1",
+ "resize-observer-polyfill": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-select": {
+ "version": "14.16.6",
+ "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz",
+ "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.1.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
+ "node_modules/rc-virtual-list": {
+ "version": "3.18.3",
+ "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.3.tgz",
+ "integrity": "sha512-s1/bZQY2uwnmgXYeXxJkk2cSTz1cdUPDCrxAq/y1WQM115HilFFIvLi+JVFfkD4xCq3TZxGM17FQH4NLesWfwg==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "classnames": "^2.2.6",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -5600,11 +6914,12 @@
"license": "MIT"
},
"node_modules/react-markdown": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
- "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz",
+ "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==",
"dependencies": {
"@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"hast-util-to-jsx-runtime": "^2.0.0",
"html-url-attributes": "^3.0.0",
@@ -5794,9 +7109,9 @@
}
},
"node_modules/remark-gfm": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
- "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
+"version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-gfm": "^3.0.0",
@@ -5826,9 +7141,9 @@
}
},
"node_modules/remark-rehype": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz",
- "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==",
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz",
+ "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
@@ -5863,6 +7178,11 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
"node_modules/resolve": {
"version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
@@ -6278,11 +7598,11 @@
}
},
"node_modules/style-to-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.7.tgz",
- "integrity": "sha512-uSjr59G5u6fbxUfKbb8GcqMGT3Xs9v5IbPkjb0S16GyOeBLAzSRK0CixBv5YrYvzO6TDLzIS6QCn78tkqWngPw==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
+ "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
"dependencies": {
- "inline-style-parser": "0.2.3"
+ "inline-style-parser": "0.2.4"
}
},
"node_modules/supports-color": {
@@ -6646,9 +7966,9 @@
}
},
"node_modules/usehooks-ts": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz",
- "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
+ "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
"dependencies": {
"lodash.debounce": "^4.0.8"
},
@@ -6656,7 +7976,7 @@
"node": ">=16.15.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17 || ^18"
+ "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/vfile": {
diff --git a/FRONTEND_V2/package.json b/FRONTEND_V2/package.json
index 6d6f2c4..2766825 100644
--- a/FRONTEND_V2/package.json
+++ b/FRONTEND_V2/package.json
@@ -6,17 +6,19 @@
"scripts": {
"dev": "vite",
"build": "vite build",
- "lint": "eslint . --ext js,jsx --report-unused-disable-directives",
+ "lint": "eslint --config eslint.config.js src/",
+ "lint:html": "eslint --config eslint.config.js src/ --format html > eslint-report.html",
"preview": "vite preview"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"ace-builds": "^1.32.9",
- "axios": "^1.7.4",
+ "axios": "1.9.0",
"bootstrap": "5.3.3",
"bootstrap-icons": "^1.11.3",
"dompurify": "^3.1.3",
- "esbuild": "^0.24.0",
+ "prop-types": "^15.8.1",
+ "rc-select": "^14.16.6",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0",
@@ -30,14 +32,16 @@
"usehooks-ts": "^3.1.0"
},
"devDependencies": {
+ "@eslint/js": "^9.21.0",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
- "eslint": "^8.57.0",
- "eslint-plugin-react": "^7.34.1",
+ "eslint": "^9.21.0",
+ "eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
- "vite": "^5.4.6",
+ "globals": "^16.0.0",
+ "vite": "^5.2.0",
"vite-plugin-chunk-split": "^0.5.0"
}
}
diff --git a/FRONTEND_V2/src/App.jsx b/FRONTEND_V2/src/App.jsx
index a24770a..334f5a2 100644
--- a/FRONTEND_V2/src/App.jsx
+++ b/FRONTEND_V2/src/App.jsx
@@ -1,18 +1,38 @@
-import SeeQuizzProblemPage from "./pages/SeeQuizzProblemPage.jsx";
-
-import("../node_modules/bootstrap/dist/js/bootstrap.min.js")
-
-
+import SeeQuizzProblemPage from "./pages/user/SeeQuizzProblemPage.jsx";
import Header from "./components/Header.jsx";
-import ProblemsListPage from "./pages/ProblemsListPage.jsx";
-import SendsListPage from "./pages/SendsListPage.jsx";
-import StatsPage from "./pages/StatsPage.jsx";
-import SeeProblemPage from "./pages/SeeProblemPage.jsx";
+import ProblemsListPage from "./pages/user/ProblemsListPage.jsx";
+import SendsListPage from "./pages/user/SendsListPage.jsx";
+import StatsPage from "./pages/user/StatsPage.jsx";
+import SeeProblemPage from "./pages/user/SeeProblemPage.jsx";
-import SeeSendPage from "./pages/SeeSendPage.jsx";
-import StatusesPage from "./pages/StatusesPage.jsx";
+import SeeSendPage from "./pages/user/SeeSendPage.jsx";
+import StatusesPage from "./pages/user/StatusesPage.jsx";
import {BrowserRouter, Route, Routes} from "react-router-dom";
-import LoginPage from "./pages/LoginPage.jsx";
+import LoginPage from "./pages/user/LoginPage.jsx";
+import ChampsPage from "./pages/user/ChampsPage.jsx";
+import {AdminChampsPage} from "./pages/champs/AdminChampsPage.jsx";
+import {AdminChampsDetailPage} from "./pages/champs/AdminChampsDetailPage.jsx";
+import {AdminProblemsPage} from "./pages/admin/problems/AdminProblemsPage.jsx";
+import {AdminCheckersPage} from "./pages/admin/checkers/AdminCheckersPage.jsx";
+import {AdminChampsDetailRatingPage} from "./pages/champs/AdminChampsDetailRatingPage.jsx";
+import {AdminSeeSendPage} from "./pages/admin/AdminSeeSendPage.jsx";
+import {NotFound} from "./pages/NotFound.jsx";
+import {AdminUsersDetailPage} from "./pages/admin/AdminUsersDetailPage.jsx";
+import {AdminChampsCreate} from "./pages/champs/AdminChampsCreate.jsx";
+import {AdminChampsDetailProblemsPage} from "./pages/champs/competitionProblems/AdminChampsDetailProblemsPage.jsx";
+import {AdminChampsDetailProblemsLinkPage} from "./pages/champs/competitionProblems/AdminChampsDetailProblemsLinkPage.jsx";
+import {AdminChampsDetailProblemsEditPage} from "./pages/champs/competitionProblems/AdminChampsDetailProblemsEditPage.jsx";
+import {AdminUsersDetailCheckersPage} from "./pages/admin/AdminUsersDetailCheckersPage.jsx";
+import {AdminCheckersEditPage} from "./pages/admin/checkers/AdminCheckersEditPage.jsx";
+import {AdminProblemsPageCreate} from "./pages/admin/problems/AdminProblemsPageCreate.jsx";
+import {AdminProblemsPageEdit} from "./pages/admin/problems/AdminProblemsPageEdit.jsx";
+import {AdminCheckersCreatePage} from "./pages/admin/checkers/AdminCheckersCreatePage.jsx";
+import {Profile} from "./pages/user/Profile.jsx";
+import {AdminUserCreatePage} from "./pages/admin/users/AdminUserCreatePage.jsx";
+import {AdminUsersPage} from "./pages/admin/users/AdminUsersPage.jsx";
+
+import("../node_modules/bootstrap/dist/js/bootstrap.min.js")
+
function App() {
@@ -25,13 +45,43 @@ function App() {
}/>
}/>
- }/>
- }/>
+ }/>
+ }/>
}/>
- }/>
- }/>
- }/>
+ }/>
+ }/>
+ }/>
}/>
+ }/>
+ }/>
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }
+ />
+ }
+ />
+ }/>
+ }/>
+ }/>
+
+ }/>
+ }/>
+
+ }/>
diff --git a/FRONTEND_V2/src/components/AdminHeader.jsx b/FRONTEND_V2/src/components/AdminHeader.jsx
new file mode 100644
index 0000000..222a2d9
--- /dev/null
+++ b/FRONTEND_V2/src/components/AdminHeader.jsx
@@ -0,0 +1,15 @@
+import {Link} from "react-router-dom";
+import Card from "./bootstrap/Card.jsx";
+
+export const AdminHeader = () => {
+ return (
+
+ соревнования
+ задачи
+ пользователи
+ чекеры
+ интерфейс ученика
+
+ );
+};
+
diff --git a/FRONTEND_V2/src/components/BreadcrumbsElement.jsx b/FRONTEND_V2/src/components/BreadcrumbsElement.jsx
new file mode 100644
index 0000000..ebfd4af
--- /dev/null
+++ b/FRONTEND_V2/src/components/BreadcrumbsElement.jsx
@@ -0,0 +1,34 @@
+import {Link} from "react-router-dom";
+import PropTypes from "prop-types";
+
+const BreadcrumbsElement = ({name, url, active}) => {
+
+ const isActiveClass = active ? ("active") : ("")
+
+ return (
+
+ url ? (
+
+ {name}
+
+ ) : (
+
+ {name}
+
+ )
+
+ );
+};
+
+BreadcrumbsElement.propTypes = {
+ name: PropTypes.string.isRequired,
+ url: PropTypes.string,
+ active: PropTypes.bool
+};
+
+BreadcrumbsElement.defaultProps = {
+ url: null,
+ active: false
+};
+
+export default BreadcrumbsElement;
diff --git a/FRONTEND_V2/src/components/BreadcrumpsRoot.jsx b/FRONTEND_V2/src/components/BreadcrumpsRoot.jsx
new file mode 100644
index 0000000..32be2f4
--- /dev/null
+++ b/FRONTEND_V2/src/components/BreadcrumpsRoot.jsx
@@ -0,0 +1,20 @@
+import Card from "./bootstrap/Card.jsx";
+import PropTypes from "prop-types";
+
+const BreadcrumbsRoot = ({children}) => {
+ return (
+
+
+
+ );
+};
+
+BreadcrumbsRoot.propTypes = {
+ children: PropTypes.node.isRequired
+};
+
+export default BreadcrumbsRoot;
diff --git a/FRONTEND_V2/src/components/CompetitionCard.jsx b/FRONTEND_V2/src/components/CompetitionCard.jsx
new file mode 100644
index 0000000..ea535e4
--- /dev/null
+++ b/FRONTEND_V2/src/components/CompetitionCard.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import Card from "./bootstrap/Card.jsx";
+import PropTypes from "prop-types";
+
+export const CompetitionCard = ({name, description, children, id}) => {
+ return (
+
+ Идет
+
+
{name}
id={id || "0000"}
+
+ {description}
+
+ {children}
+
+ );
+};
+
+CompetitionCard.propTypes = {
+ name: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ children: PropTypes.node,
+ id: PropTypes.string,
+}
\ No newline at end of file
diff --git a/FRONTEND_V2/src/components/DeleteButton.jsx b/FRONTEND_V2/src/components/DeleteButton.jsx
new file mode 100644
index 0000000..3d4e89b
--- /dev/null
+++ b/FRONTEND_V2/src/components/DeleteButton.jsx
@@ -0,0 +1,53 @@
+import React, {useState} from 'react';
+import constants from "../utils/consts.js";
+import axios from "axios";
+import {useNavigate} from "react-router-dom";
+import PropTypes from "prop-types";
+
+export const DeleteButton = ({
+ url,
+ }) => {
+
+ const [disabled, setDisabled] = useState(false)
+
+ const navigate = useNavigate()
+
+ const conf = {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${localStorage.getItem(constants.LOCALSTORAGE_JWT)}`
+ }
+ }
+
+ const onClick = () => {
+ setDisabled(true)
+
+ axios.delete(url, conf)
+ .then(() => navigate("/admin/champs"))
+ .finally(() => setDisabled(false))
+ }
+
+ return (
+
+ );
+};
+
+DeleteButton.propTypes = {
+ url: PropTypes.string.isRequired
+};
\ No newline at end of file
diff --git a/FRONTEND_V2/src/components/Header.jsx b/FRONTEND_V2/src/components/Header.jsx
index 679fcfd..8c4a951 100644
--- a/FRONTEND_V2/src/components/Header.jsx
+++ b/FRONTEND_V2/src/components/Header.jsx
@@ -1,6 +1,9 @@
import "./css/Header.css"
-import {Link, useNavigate} from "react-router-dom";
+import {Link, useLocation, useNavigate} from "react-router-dom";
import constants from "../utils/consts.js";
+import useCachedGetAPI from "../hooks/useGetAPI.js";
+import {useEffect} from "react";
+
const Header = () => {
@@ -8,11 +11,25 @@ const Header = () => {
let isAuthed = localStorage.getItem(constants.LOCALSTORAGE_AUTH_KEY) === "true"
+ const [profile, update] = useCachedGetAPI("/api/users/me", () => {
+ }, {});
+
+ console.log(profile)
+
+ const params = useLocation()
+ const compId = params.pathname.split("/")[2]
+
+ useEffect(() => {
+ update()
+ }, []);
+
const onLogoutButtonClick = () => {
localStorage.clear()
navigate("/")
}
+ // console.log("HEADER", params, needPath)
+
return (