From ae2f15b5acfd3392a8615d1cc6f69c53302b6aae Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 19:52:42 +0300 Subject: [PATCH 1/6] refactor(backend): moved files + moved files from backend/app to backend folder + fixed imports + fixed dev/start scripts (run as module now) + added root gitignore + added install.sh to prepare backend venv + added vscode basic settings for code-server --- .env.example | 2 +- .gitignore | 156 +++++++++++++++++- .vscode/settings.json | 5 +- backend/{app => }/__init__.py | 0 backend/alembic/env.py | 2 +- backend/{app => }/api/__init__.py | 0 backend/{app => }/api/admin.py | 14 +- backend/{app => }/api/auth.py | 6 +- backend/{app => }/api/card.py | 2 +- backend/{app => }/api/password_recovery.py | 10 +- backend/{app => }/api/public.py | 12 +- backend/{app => }/api/user.py | 6 +- backend/{app => }/app.py | 6 +- backend/{app => }/config.py | 0 backend/{app => }/core/__init__.py | 0 backend/{app => }/core/auth.py | 6 +- backend/{app => }/core/smtp.py | 2 +- backend/{app => }/core/user.py | 4 +- backend/{app => }/db/__init__.py | 0 backend/{app => }/db/cleanup.py | 4 +- backend/{app => }/db/models/__init__.py | 0 backend/{app => }/db/models/base.py | 0 backend/{app => }/db/models/card.py | 2 +- .../db/models/password_recovery_code.py | 2 +- backend/{app => }/db/models/refresh_token.py | 2 +- backend/{app => }/db/models/settings.py | 2 +- backend/{app => }/db/models/user.py | 4 +- backend/{app => }/db/models/user_role.py | 2 +- backend/{app => }/db/session.py | 2 +- backend/dev.py | 28 +++- backend/{app => }/enums/__init__.py | 0 backend/{app => }/enums/settings.py | 0 backend/{app => }/enums/user_role.py | 0 backend/{app => }/helpers/__init__.py | 0 backend/{app => }/helpers/delay_to_minimum.py | 0 .../helpers/get_setting_typed_value.py | 2 +- backend/{app => }/helpers/now.py | 0 backend/requirements.txt | Bin 2044 -> 964 bytes backend/{app => }/schemas/__init__.py | 0 backend/{app => }/schemas/auth.py | 0 backend/{app => }/schemas/card.py | 0 .../{app => }/schemas/password_recovery.py | 0 backend/{app => }/schemas/public_settings.py | 0 backend/{app => }/schemas/settings.py | 0 backend/{app => }/schemas/user.py | 2 +- backend/{app => }/schemas/validators.py | 0 backend/{app => }/schemas/version.py | 0 backend/start.py | 28 +++- backend/test/api/test_admin.py | 6 +- backend/test/api/test_user.py | 22 +-- frontend/package.json | 2 +- install.sh | 4 + 52 files changed, 270 insertions(+), 77 deletions(-) rename backend/{app => }/__init__.py (100%) rename backend/{app => }/api/__init__.py (100%) rename backend/{app => }/api/admin.py (93%) rename backend/{app => }/api/auth.py (94%) rename backend/{app => }/api/card.py (97%) rename backend/{app => }/api/password_recovery.py (92%) rename backend/{app => }/api/public.py (86%) rename backend/{app => }/api/user.py (90%) rename backend/{app => }/app.py (88%) rename backend/{app => }/config.py (100%) rename backend/{app => }/core/__init__.py (100%) rename backend/{app => }/core/auth.py (96%) rename backend/{app => }/core/smtp.py (98%) rename backend/{app => }/core/user.py (85%) rename backend/{app => }/db/__init__.py (100%) rename backend/{app => }/db/cleanup.py (93%) rename backend/{app => }/db/models/__init__.py (100%) rename backend/{app => }/db/models/base.py (100%) rename backend/{app => }/db/models/card.py (98%) rename backend/{app => }/db/models/password_recovery_code.py (97%) rename backend/{app => }/db/models/refresh_token.py (97%) rename backend/{app => }/db/models/settings.py (95%) rename backend/{app => }/db/models/user.py (96%) rename backend/{app => }/db/models/user_role.py (92%) rename backend/{app => }/db/session.py (97%) rename backend/{app => }/enums/__init__.py (100%) rename backend/{app => }/enums/settings.py (100%) rename backend/{app => }/enums/user_role.py (100%) rename backend/{app => }/helpers/__init__.py (100%) rename backend/{app => }/helpers/delay_to_minimum.py (100%) rename backend/{app => }/helpers/get_setting_typed_value.py (91%) rename backend/{app => }/helpers/now.py (100%) rename backend/{app => }/schemas/__init__.py (100%) rename backend/{app => }/schemas/auth.py (100%) rename backend/{app => }/schemas/card.py (100%) rename backend/{app => }/schemas/password_recovery.py (100%) rename backend/{app => }/schemas/public_settings.py (100%) rename backend/{app => }/schemas/settings.py (100%) rename backend/{app => }/schemas/user.py (97%) rename backend/{app => }/schemas/validators.py (100%) rename backend/{app => }/schemas/version.py (100%) create mode 100755 install.sh diff --git a/.env.example b/.env.example index 18fe3bc..1863cb2 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ # in frontend's environment.*.ts, as well as nginx.conf API_PATH=/api -# App log level, mainly for the unicorn command in start.py +# App log level, mainly for the uvicorn command in start.py # critical, error, warning, info, debug, trace LOG_LEVEL=warning diff --git a/.gitignore b/.gitignore index 30bc162..dae6ade 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,155 @@ -/node_modules \ No newline at end of file +**/cardholder_pwa.db + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite logs files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# PYTHON +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41..9ee49cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,5 @@ { -} \ No newline at end of file + "python.terminal.activateEnvironment": true, + "python.venvPath": ".venv", + "python.languageServer": "None" +} diff --git a/backend/app/__init__.py b/backend/__init__.py similarity index 100% rename from backend/app/__init__.py rename to backend/__init__.py diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 7c7717e..a802f20 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,4 +1,4 @@ -from app.db import Base, async_engine +from backend.db import Base, async_engine from logging.config import fileConfig import asyncio from alembic import context diff --git a/backend/app/api/__init__.py b/backend/api/__init__.py similarity index 100% rename from backend/app/api/__init__.py rename to backend/api/__init__.py diff --git a/backend/app/api/admin.py b/backend/api/admin.py similarity index 93% rename from backend/app/api/admin.py rename to backend/api/admin.py index 21bf1b7..074578f 100644 --- a/backend/app/api/admin.py +++ b/backend/api/admin.py @@ -2,13 +2,13 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select -import app.schemas as schemas, app.enums as enums -from app.db import models -from app.core import auth -from app.db import get_async_session -from app.core.user import delete_user -from app.core.smtp import EmailSender -from app.helpers import get_setting_typed_value +import backend.schemas as schemas, backend.enums as enums +from backend.db import models +from backend.core import auth +from backend.db import get_async_session +from backend.core.user import delete_user +from backend.core.smtp import EmailSender +from backend.helpers import get_setting_typed_value router = APIRouter(tags=["admin"], prefix="/admin") diff --git a/backend/app/api/auth.py b/backend/api/auth.py similarity index 94% rename from backend/app/api/auth.py rename to backend/api/auth.py index 86d1069..d832873 100644 --- a/backend/app/api/auth.py +++ b/backend/api/auth.py @@ -1,11 +1,11 @@ from fastapi import APIRouter, Depends, HTTPException, status, Request -import app.db.models as models, app.db as db, app.schemas as schemas, app.core.auth as auth +import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth from fastapi.security import OAuth2PasswordRequestForm -from app.helpers import now +from backend.helpers import now from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from sqlalchemy.orm import selectinload -from app.helpers import delay_to_minimum +from backend.helpers import delay_to_minimum router = APIRouter(tags=["auth"]) diff --git a/backend/app/api/card.py b/backend/api/card.py similarity index 97% rename from backend/app/api/card.py rename to backend/api/card.py index 479bbe7..b76bf18 100644 --- a/backend/app/api/card.py +++ b/backend/api/card.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -import app.db as db, app.db.models as models, app.schemas as schemas, app.core.auth as auth +import backend.db as db, backend.db.models as models, backend.schemas as schemas, backend.core.auth as auth from sqlalchemy import select router = APIRouter(tags=["card"]) diff --git a/backend/app/api/password_recovery.py b/backend/api/password_recovery.py similarity index 92% rename from backend/app/api/password_recovery.py rename to backend/api/password_recovery.py index a528801..3b2b330 100644 --- a/backend/app/api/password_recovery.py +++ b/backend/api/password_recovery.py @@ -1,16 +1,16 @@ from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import update -import app.db.models as models, app.db as db, app.schemas as schemas, app.core.auth as auth +import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth import secrets from datetime import timedelta -from app.helpers import now -from app.core.smtp import EmailSender -from app.config import Config +from backend.helpers import now +from backend.core.smtp import EmailSender +from backend.config import Config from sqlalchemy import select, desc from sqlalchemy.orm import selectinload import asyncio -from app.helpers import delay_to_minimum +from backend.helpers import delay_to_minimum router = APIRouter(tags=["password recovery"], prefix="/recovery") diff --git a/backend/app/api/public.py b/backend/api/public.py similarity index 86% rename from backend/app/api/public.py rename to backend/api/public.py index d8bb0dc..ab28616 100644 --- a/backend/app/api/public.py +++ b/backend/api/public.py @@ -4,12 +4,12 @@ from sqlalchemy.ext.asyncio import AsyncSession from typing import Any -from app.db import get_async_session -from app.db import models -import app.schemas as schemas -from app.enums import ESettingKey -from app.helpers import get_setting_typed_value -from app.config import Config +from backend.db import get_async_session +from backend.db import models +import backend.schemas as schemas +from backend.enums import ESettingKey +from backend.helpers import get_setting_typed_value +from backend.config import Config router = APIRouter(tags=["public"], prefix="/public") diff --git a/backend/app/api/user.py b/backend/api/user.py similarity index 90% rename from backend/app/api/user.py rename to backend/api/user.py index 6d41386..7530c2e 100644 --- a/backend/app/api/user.py +++ b/backend/api/user.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -import app.db.models as models, app.db as db, app.schemas as schemas, app.core.auth as auth, app.enums as enums -from app.core.user import delete_user as _delete_user +import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth, backend.enums as enums +from backend.core.user import delete_user as _delete_user from sqlalchemy import select -from app.helpers import delay_to_minimum +from backend.helpers import delay_to_minimum router = APIRouter(tags=["user"]) diff --git a/backend/app/app.py b/backend/app.py similarity index 88% rename from backend/app/app.py rename to backend/app.py index cd76b92..c192959 100644 --- a/backend/app/app.py +++ b/backend/app.py @@ -1,6 +1,6 @@ from fastapi import FastAPI -from app.config import Config -from app.api import ( +from backend.config import Config +from backend.api import ( auth_router, user_router, card_router, @@ -9,7 +9,7 @@ admin_router, ) import asyncio -from app.db import cleanup +from backend.db import cleanup from contextlib import asynccontextmanager diff --git a/backend/app/config.py b/backend/config.py similarity index 100% rename from backend/app/config.py rename to backend/config.py diff --git a/backend/app/core/__init__.py b/backend/core/__init__.py similarity index 100% rename from backend/app/core/__init__.py rename to backend/core/__init__.py diff --git a/backend/app/core/auth.py b/backend/core/auth.py similarity index 96% rename from backend/app/core/auth.py rename to backend/core/auth.py index 4d1bb13..4a6520a 100644 --- a/backend/app/core/auth.py +++ b/backend/core/auth.py @@ -2,12 +2,12 @@ from fastapi.security import OAuth2PasswordBearer from jose import jwt, JWTError from datetime import timedelta, datetime -from app.helpers import now +from backend.helpers import now import bcrypt -import app.schemas as schemas, app.db as db, app.db.models as models, app.enums as enums +import backend.schemas as schemas, backend.db as db, backend.db.models as models, backend.enums as enums from typing import cast import secrets -from app.config import Config +from backend.config import Config from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, or_ diff --git a/backend/app/core/smtp.py b/backend/core/smtp.py similarity index 98% rename from backend/app/core/smtp.py rename to backend/core/smtp.py index c839284..38b4f89 100644 --- a/backend/app/core/smtp.py +++ b/backend/core/smtp.py @@ -2,7 +2,7 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from typing import Optional -from app.config import Config +from backend.config import Config import logging diff --git a/backend/app/core/user.py b/backend/core/user.py similarity index 85% rename from backend/app/core/user.py rename to backend/core/user.py index f8fbec5..76b80bd 100644 --- a/backend/app/core/user.py +++ b/backend/core/user.py @@ -1,8 +1,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import delete from fastapi import HTTPException -from app.db.models import User, Card, RefreshToken, PasswordRecoveryCode -import app.enums as enums +from backend.db.models import User, Card, RefreshToken, PasswordRecoveryCode +import backend.enums as enums async def delete_user( diff --git a/backend/app/db/__init__.py b/backend/db/__init__.py similarity index 100% rename from backend/app/db/__init__.py rename to backend/db/__init__.py diff --git a/backend/app/db/cleanup.py b/backend/db/cleanup.py similarity index 93% rename from backend/app/db/cleanup.py rename to backend/db/cleanup.py index 8342fd8..66bc117 100644 --- a/backend/app/db/cleanup.py +++ b/backend/db/cleanup.py @@ -4,8 +4,8 @@ from .models import RefreshToken, PasswordRecoveryCode from .session import _async_session_maker import logging -from app.config import Config -from app.helpers import now +from backend.config import Config +from backend.helpers import now async def cleanup(): diff --git a/backend/app/db/models/__init__.py b/backend/db/models/__init__.py similarity index 100% rename from backend/app/db/models/__init__.py rename to backend/db/models/__init__.py diff --git a/backend/app/db/models/base.py b/backend/db/models/base.py similarity index 100% rename from backend/app/db/models/base.py rename to backend/db/models/base.py diff --git a/backend/app/db/models/card.py b/backend/db/models/card.py similarity index 98% rename from backend/app/db/models/card.py rename to backend/db/models/card.py index 207e60d..47446a7 100644 --- a/backend/app/db/models/card.py +++ b/backend/db/models/card.py @@ -1,4 +1,4 @@ -from app.helpers import now +from backend.helpers import now from .base import Base from sqlalchemy import Integer, String, Text, DateTime, ForeignKey, text, Boolean from sqlalchemy.orm import relationship, mapped_column, Mapped diff --git a/backend/app/db/models/password_recovery_code.py b/backend/db/models/password_recovery_code.py similarity index 97% rename from backend/app/db/models/password_recovery_code.py rename to backend/db/models/password_recovery_code.py index 8950a80..b513156 100644 --- a/backend/app/db/models/password_recovery_code.py +++ b/backend/db/models/password_recovery_code.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import relationship, mapped_column, Mapped from datetime import datetime from typing import TYPE_CHECKING -from app.helpers import now +from backend.helpers import now if TYPE_CHECKING: from .user import User diff --git a/backend/app/db/models/refresh_token.py b/backend/db/models/refresh_token.py similarity index 97% rename from backend/app/db/models/refresh_token.py rename to backend/db/models/refresh_token.py index 59aac0b..06152fd 100644 --- a/backend/app/db/models/refresh_token.py +++ b/backend/db/models/refresh_token.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import relationship, mapped_column, Mapped from datetime import datetime from typing import TYPE_CHECKING, Optional -from app.helpers import now +from backend.helpers import now if TYPE_CHECKING: from .user import User diff --git a/backend/app/db/models/settings.py b/backend/db/models/settings.py similarity index 95% rename from backend/app/db/models/settings.py rename to backend/db/models/settings.py index 5d98c1a..bf0c1f9 100644 --- a/backend/app/db/models/settings.py +++ b/backend/db/models/settings.py @@ -2,7 +2,7 @@ from sqlalchemy import String, DateTime, text from sqlalchemy.orm import mapped_column, Mapped from datetime import datetime -from app.helpers import now +from backend.helpers import now class Setting(Base): diff --git a/backend/app/db/models/user.py b/backend/db/models/user.py similarity index 96% rename from backend/app/db/models/user.py rename to backend/db/models/user.py index 1ef47b7..6a1f030 100644 --- a/backend/app/db/models/user.py +++ b/backend/db/models/user.py @@ -3,8 +3,8 @@ from sqlalchemy.orm import relationship, mapped_column, Mapped from datetime import datetime from typing import TYPE_CHECKING -from app.helpers import now -from app.enums import EUserRole +from backend.helpers import now +from backend.enums import EUserRole if TYPE_CHECKING: diff --git a/backend/app/db/models/user_role.py b/backend/db/models/user_role.py similarity index 92% rename from backend/app/db/models/user_role.py rename to backend/db/models/user_role.py index 8bfd98d..7a98547 100644 --- a/backend/app/db/models/user_role.py +++ b/backend/db/models/user_role.py @@ -2,7 +2,7 @@ from sqlalchemy import Enum as SQLEnum from sqlalchemy.orm import Mapped, mapped_column, relationship from typing import TYPE_CHECKING -from app.enums import EUserRole +from backend.enums import EUserRole if TYPE_CHECKING: diff --git a/backend/app/db/session.py b/backend/db/session.py similarity index 97% rename from backend/app/db/session.py rename to backend/db/session.py index 532945a..040b7be 100644 --- a/backend/app/db/session.py +++ b/backend/db/session.py @@ -1,4 +1,4 @@ -from app.config import Config +from backend.config import Config from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from typing import AsyncGenerator from sqlalchemy.ext.asyncio import AsyncSession diff --git a/backend/dev.py b/backend/dev.py index 03f3eb9..16a749e 100644 --- a/backend/dev.py +++ b/backend/dev.py @@ -1,18 +1,34 @@ import subprocess +import os def run_migrations(): print("Running Alembic migrations...") - result = subprocess.run(["alembic", "upgrade", "head"], check=False) + result = subprocess.run( + [ + "alembic", + "-c", + os.path.join(os.path.dirname(__file__), "alembic.ini"), + "upgrade", + "head", + ] + ) if result.returncode != 0: - print("Alembic migrations failed (continuing anyway)") - else: - print("Alembic migrations completed") + raise Exception("Alembic migrations failed. Exiting.") + print("Alembic migrations completed.") if __name__ == "__main__": run_migrations() try: - subprocess.run(["fastapi", "dev", "app/app.py"]) + subprocess.run( + [ + "fastapi", + "dev", + os.path.join(os.path.dirname(__file__), "app.py"), + "--host=0.0.0.0", + "--port=8000", + ] + ) except KeyboardInterrupt as e: - print("Dev server shutdown by user") + print("Dev server shutdown by user.") diff --git a/backend/app/enums/__init__.py b/backend/enums/__init__.py similarity index 100% rename from backend/app/enums/__init__.py rename to backend/enums/__init__.py diff --git a/backend/app/enums/settings.py b/backend/enums/settings.py similarity index 100% rename from backend/app/enums/settings.py rename to backend/enums/settings.py diff --git a/backend/app/enums/user_role.py b/backend/enums/user_role.py similarity index 100% rename from backend/app/enums/user_role.py rename to backend/enums/user_role.py diff --git a/backend/app/helpers/__init__.py b/backend/helpers/__init__.py similarity index 100% rename from backend/app/helpers/__init__.py rename to backend/helpers/__init__.py diff --git a/backend/app/helpers/delay_to_minimum.py b/backend/helpers/delay_to_minimum.py similarity index 100% rename from backend/app/helpers/delay_to_minimum.py rename to backend/helpers/delay_to_minimum.py diff --git a/backend/app/helpers/get_setting_typed_value.py b/backend/helpers/get_setting_typed_value.py similarity index 91% rename from backend/app/helpers/get_setting_typed_value.py rename to backend/helpers/get_setting_typed_value.py index 46efc91..a63f46b 100644 --- a/backend/app/helpers/get_setting_typed_value.py +++ b/backend/helpers/get_setting_typed_value.py @@ -1,4 +1,4 @@ -from app.enums import ESettingType +from backend.enums import ESettingType def get_setting_typed_value(value: str, value_type: str): diff --git a/backend/app/helpers/now.py b/backend/helpers/now.py similarity index 100% rename from backend/app/helpers/now.py rename to backend/helpers/now.py diff --git a/backend/requirements.txt b/backend/requirements.txt index ba7500beb712a6cefbf1b6353c8e373dd01df824..f6034fad0ec78a751462a59627235fdd9df9ed70 100644 GIT binary patch literal 964 zcmX|AyOQG|5X|`(Q3A`44HRs;wXV9WjV^Lwge6u8IKcMm@2>~j7xplZo}Nb8kpDVc zOy9R$3f?m1%yPE6?*$h-E|~H@6jjX7lr@@6d3#Fps||hM@izi_^?F%r4=$cKj)XTY z-p@L%u~7B~NSjpb#rC~y<(79`?wGz1#>71)YG?J__XjQs1Q(K86!q#jGO1G-lZq4O zbz6=RZ16d*WeOfWD?6iyTua0odYDC7ce*KO2US9%BnciDm5Yj)+j1ecT*z0X0RxI+ zQ#K}JyfYxbL6KyW5Vc^322&|fhXm~)9Dl30gy5=3JKoj652eckXxPDn(}o1H-s<2l z77}4ewED3ATgm#0C68)`wgPG)cTs5`!reEvG!Z*PLKGYky~Zzf!E}m9whY(gDmQ?r z^sGkfX#g~0(Ot(8#ex&@xT@^y9(3Wj>ZnpS4`EQd4$2p@0JsUh{U10zmX&-1E!eMh zT#PSR7HK3iPGB;GI^a+*)KqJbQ%EA3OlN9_P>jD(kd}W2zeBFeRKAbAA|H@T14H=KAg-C zw$6w{GlapKU2iZ#s)-x?4qz8Tq&i9NzpJ9Bi*@wxNWd+-n{y6&Hl#aDi^=RC+!h}v literal 2044 zcmZWqOK;j>5ZrU6{uDMN6?nr2mchJwi=OIU>Rj) z8NRP}!tZ8?`8bML!^+&uH^cK!BU=MojZ6o8CqB#=d;)Q@&48FXZy}cOK70Ol$S@r; z6kv4*zpbtDW`+rTC-wjYxlB+Qt14g*geqnQd2gd)h3(w`qkARW5sDp=V}TkLj-aND z9g&0G5fu=3vZ}9k<~)d>dZrb$XkfpBafPUL&-l)go+X&9fJ(V?S5#ctJ9c_+bNh{7 zWWK_8>l{@-tB!uJVQXL|E9pZvIbKvK2a@XCqB1aG+~^lQ9v#Q}O1x^=OGG7ty@nwf%(mWaJ-Re#Y}1{27@(tU98zBWGAwuwM`(L!=g;>~k=ohSGtN zi0)v2!S6Mc9HXS;0ns|(_HI+pLgN#>IIRoRLC#SPJE|Nf_;q-`=^2nQH4N(&z70I{ zA$r<>vM#`lEE{k;J4PKAHzGK5HL8p&A}yKc!TaMOinFGGeN-n{_YZrrXZz}z)uZV1+10IjPOLReQP_B!wJkfKQ3a6*S=Aed~~ZgL%h7)skgg9&lmfS%ASy+ z@LcgPz}~W!S>HWfC{8=C%J{!)=5wpLi+>?aq;?`rwXCXy8>mamYsxOftyR{Tnz7Fu zxnDguJ>{Vbq$!jBtg7>>b;r^Lr#8Cs9p*^a<_sh!idm=L5g{&6x^+#c9IIBQm{RS~ T1<#kT9D&@qGJ(?6MHT-5V#Fjf diff --git a/backend/app/schemas/__init__.py b/backend/schemas/__init__.py similarity index 100% rename from backend/app/schemas/__init__.py rename to backend/schemas/__init__.py diff --git a/backend/app/schemas/auth.py b/backend/schemas/auth.py similarity index 100% rename from backend/app/schemas/auth.py rename to backend/schemas/auth.py diff --git a/backend/app/schemas/card.py b/backend/schemas/card.py similarity index 100% rename from backend/app/schemas/card.py rename to backend/schemas/card.py diff --git a/backend/app/schemas/password_recovery.py b/backend/schemas/password_recovery.py similarity index 100% rename from backend/app/schemas/password_recovery.py rename to backend/schemas/password_recovery.py diff --git a/backend/app/schemas/public_settings.py b/backend/schemas/public_settings.py similarity index 100% rename from backend/app/schemas/public_settings.py rename to backend/schemas/public_settings.py diff --git a/backend/app/schemas/settings.py b/backend/schemas/settings.py similarity index 100% rename from backend/app/schemas/settings.py rename to backend/schemas/settings.py diff --git a/backend/app/schemas/user.py b/backend/schemas/user.py similarity index 97% rename from backend/app/schemas/user.py rename to backend/schemas/user.py index c6d0a56..eb6377f 100644 --- a/backend/app/schemas/user.py +++ b/backend/schemas/user.py @@ -1,7 +1,7 @@ from typing import Optional, Self from pydantic import BaseModel, EmailStr, field_validator, model_validator, ConfigDict from .validators import password_validator -from app.enums import EUserRole +from backend.enums import EUserRole from datetime import datetime diff --git a/backend/app/schemas/validators.py b/backend/schemas/validators.py similarity index 100% rename from backend/app/schemas/validators.py rename to backend/schemas/validators.py diff --git a/backend/app/schemas/version.py b/backend/schemas/version.py similarity index 100% rename from backend/app/schemas/version.py rename to backend/schemas/version.py diff --git a/backend/start.py b/backend/start.py index 408dec2..b1cc461 100644 --- a/backend/start.py +++ b/backend/start.py @@ -1,18 +1,34 @@ import subprocess import uvicorn import os +from dotenv import load_dotenv + +load_dotenv() +log_level = os.getenv("LOG_LEVEL", "WARNING").lower() def run_migrations(): print("Running Alembic migrations...") - result = subprocess.run(["alembic", "upgrade", "head"], check=False) + result = subprocess.run( + [ + "alembic", + "-c", + os.path.join(os.path.dirname(__file__), "alembic.ini"), + "upgrade", + "head", + ], + check=False, + ) if result.returncode != 0: - print("Alembic migrations failed (continuing anyway)") - else: - print("Alembic migrations completed") + raise Exception("Alembic migrations failed. Exiting.") + print("Alembic migrations completed.") if __name__ == "__main__": run_migrations() - log_level = os.getenv("LOG_LEVEL") or "warning" - uvicorn.run("app.app:app", host="0.0.0.0", port=8000, log_level=log_level) + uvicorn.run( + "backend.app:app", + host="0.0.0.0", + port=8000, + log_level=log_level, + ) diff --git a/backend/test/api/test_admin.py b/backend/test/api/test_admin.py index 9ace75f..48e6efe 100644 --- a/backend/test/api/test_admin.py +++ b/backend/test/api/test_admin.py @@ -4,9 +4,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from fastapi import HTTPException from typing import cast, Any -from app.api.admin import admin_delete_user -from app.db.models import User -from app.enums import EUserRole +from backend.api.admin import admin_delete_user +from backend.db.models import User +from backend.enums import EUserRole @pytest.mark.asyncio diff --git a/backend/test/api/test_user.py b/backend/test/api/test_user.py index 8fe58aa..9ef9e03 100644 --- a/backend/test/api/test_user.py +++ b/backend/test/api/test_user.py @@ -3,11 +3,11 @@ from unittest.mock import AsyncMock, MagicMock, patch from sqlalchemy.ext.asyncio import AsyncSession from fastapi import HTTPException -from app.api.user import create_user, update_user -from app.core.auth import verify_password -from app.db.models import User -from app.schemas import UserCreate, UserUpdate -from app.enums import EUserRole +from backend.api.user import create_user, update_user +from backend.core.auth import verify_password +from backend.db.models import User +from backend.schemas import UserCreate, UserUpdate +from backend.enums import EUserRole def _get_user_create( @@ -41,7 +41,7 @@ async def test_user_should_create_owner(): session_mock.add.return_value = None with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False @@ -61,7 +61,7 @@ async def test_user_should_create_member(): session_mock.add.return_value = None with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False @@ -77,7 +77,7 @@ async def test_user_should_not_create_if_creds_taken(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -96,7 +96,7 @@ async def test_user_should_not_update_if_creds_taken(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -115,7 +115,7 @@ async def test_user_should_update_with_no_password(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await update_user(user, session_mock, current_user) @@ -139,7 +139,7 @@ async def test_user_should_update_with_password(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "app.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth.is_creds_taken", new_callable=AsyncMock ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await update_user(user, session_mock, current_user) diff --git a/frontend/package.json b/frontend/package.json index 5fbdd42..d6056fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,7 @@ "version": "0.0.13", "scripts": { "ng": "ng", - "start": "ng serve --port=4207 --open", + "start": "ng serve --port=4207 --host=0.0.0.0 --open", "start:sw": "ng serve --configuration=production --port=4207 --open", "start:nohmr": "ng serve --port=4207 --open --no-hmr", "build": "ng build --configuration=production", diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..57d42c0 --- /dev/null +++ b/install.sh @@ -0,0 +1,4 @@ +#!/bin/bash +python -m venv .venv +source .venv/bin/activate +pip install -r backend/requirements.txt \ No newline at end of file From 409440a43ff7aad8f946cc1c7e2121f867e0c341 Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 20:58:30 +0300 Subject: [PATCH 2/6] chore: dockerfile, docker compose files, version + refactor dockerfile + added healthcheck to dockerfile + save version in file instead of env + fix supervosird command for backend + public api get version from file + added docker-compose.dev.yml + removed docker-compose.yml --- Dockerfile | 30 +++++++++++++++++------------- backend/api/public.py | 24 ++++++++++++++++++------ docker-compose.dev.yml | 21 +++++++++++++++++++++ docker-compose.my.yml | 5 ----- docker-compose.pg.yml | 7 ------- docker-compose.sqlite.yml | 7 ------- docker-compose.yml | 10 ---------- supervisord.conf | 7 ++++--- 8 files changed, 60 insertions(+), 51 deletions(-) create mode 100644 docker-compose.dev.yml delete mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 8972f0c..99d3c30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,36 +8,40 @@ COPY CHANGELOG.md ./public/ RUN npm run build # Stage 2 - backend build -FROM python:3.11 AS backend-builder +FROM python:3.13 AS backend-builder WORKDIR /app RUN pip install --upgrade pip setuptools -COPY backend/requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt -COPY backend . -COPY .env* ./app +COPY backend/requirements.txt backend/requirements.txt +RUN pip install --no-cache-dir -r backend/requirements.txt +COPY backend ./backend # Stage 3 - container build -FROM python:3.11-slim +FROM python:3.13-slim AS runtime WORKDIR / ARG VERSION -ENV VERSION=$VERSION -RUN apt-get update && apt-get install -y nginx supervisor && \ - rm -rf /var/lib/apt/lists/* && \ - apt-get clean +RUN apt-get update && apt-get install -y --no-install-recommends \ + nginx supervisor curl \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean COPY --from=frontend-builder /app/dist/cardholder_pwa/browser /app/frontend -COPY --from=backend-builder /app /app/backend -COPY --from=backend-builder /usr/local/ /usr/local/ +COPY --from=backend-builder /app/backend /app/backend +COPY --from=backend-builder /usr/local/lib/python3.13 /usr/local/lib/python3.13 +COPY --from=backend-builder /usr/local/bin /usr/local/bin COPY nginx.conf /etc/nginx/nginx.conf COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY .env* /app/ -RUN pip install --upgrade pip setuptools +RUN echo "$VERSION" > /app/version # Dir for sqlite and other files RUN mkdir -p /cardholder_pwa && chmod 777 /cardholder_pwa EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -f http://localhost:8000/api/public/health || exit 1 + CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] \ No newline at end of file diff --git a/backend/api/public.py b/backend/api/public.py index ab28616..af8c73b 100644 --- a/backend/api/public.py +++ b/backend/api/public.py @@ -17,8 +17,11 @@ @router.get("/version", response_model=schemas.Version) def get_version(): - image_version = os.getenv("VERSION") or None - return {"image_version": image_version} + try: + with open("/app/version", "r") as file: + return {"image_version": file.readline()} + except FileNotFoundError: + raise HTTPException(404, "Version file not found") @router.get("/health") @@ -33,18 +36,27 @@ async def health(session: AsyncSession = Depends(get_async_session)): @router.get( "/settings", response_model=list[schemas.PublicSettingsItem], - description="Returns list of several app settings and environment variables. Key ", + description="Get list of several app settings and environment variables", ) -async def settings(session: AsyncSession = Depends(get_async_session)): +async def settings( + session: AsyncSession = Depends(get_async_session), +): result_list: list[dict[str, Any]] = [] public_keys = [ESettingKey.ALLOW_REGISTRATION] - stmt = select(models.Setting).where(models.Setting.key.in_(public_keys)) + stmt = select(models.Setting).where( + models.Setting.key.in_(public_keys) + ) result = await session.execute(stmt) settings = result.scalars() for s in settings: result_list.append( - {"key": s.key, "value": get_setting_typed_value(s.value, s.value_type)} + { + "key": s.key, + "value": get_setting_typed_value( + s.value, s.value_type + ), + } ) public_keys = ["SMTP_DISABLED"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..26ed9c6 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,21 @@ +networks: + cardholder_pwa: + driver: bridge +volumes: + cardholder_pwa_data_dev: +services: + app: + container_name: cardholder_pwa + build: + context: . + args: + VERSION: dev build + environment: + LOG_LEVEL: info + volumes: + - cardholder_pwa_data_dev:/cardholder_pwa + restart: unless-stopped + networks: + - cardholder_pwa + ports: + - '1337:80' diff --git a/docker-compose.my.yml b/docker-compose.my.yml index 6bbd904..dea1ba7 100644 --- a/docker-compose.my.yml +++ b/docker-compose.my.yml @@ -39,10 +39,5 @@ services: DB_URL: mysql+asyncmy://cardholder_user:cardholder_pass@db:3306/cardholder_db networks: - cardholder_pwa - healthcheck: - test: ["CMD-SHELL", "python -c 'import urllib.request; exit(0) if urllib.request.urlopen(\"http://localhost:8000/api/public/health\").getcode() == 200 else exit(1)'"] - interval: 10s - timeout: 5s - retries: 5 ports: - "80:80" diff --git a/docker-compose.pg.yml b/docker-compose.pg.yml index dd63a14..1651316 100644 --- a/docker-compose.pg.yml +++ b/docker-compose.pg.yml @@ -1,5 +1,3 @@ -version: "3.9" - networks: cardholder_pwa: driver: bridge @@ -31,10 +29,5 @@ services: DB_URL: postgresql+asyncpg://cardholder_user:cardholder_pass@db:5432/cardholder_db networks: - cardholder_pwa - healthcheck: - test: ["CMD-SHELL", "python -c 'import urllib.request; exit(0) if urllib.request.urlopen(\"http://localhost:8000/api/public/health\").getcode() == 200 else exit(1)'"] - interval: 10s - timeout: 5s - retries: 5 ports: - "80:80" \ No newline at end of file diff --git a/docker-compose.sqlite.yml b/docker-compose.sqlite.yml index 9c36479..a31f0f7 100644 --- a/docker-compose.sqlite.yml +++ b/docker-compose.sqlite.yml @@ -1,5 +1,3 @@ -version: '3.8' - networks: cardholder_pwa: driver: bridge @@ -11,10 +9,5 @@ services: - $HOME/.cardholder_pwa:/cardholder_pwa networks: - cardholder_pwa - healthcheck: - test: ["CMD-SHELL", "python -c 'import urllib.request; exit(0) if urllib.request.urlopen(\"http://localhost:8000/api/public/health\").getcode() == 200 else exit(1)'"] - interval: 10s - timeout: 5s - retries: 5 ports: - "80:80" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 5b575c9..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: '3.8' - -services: - app: - build: . - ports: - - "80:80" - volumes: - - $HOME/.cardholder_pwa:/cardholder_pwa - restart: unless-stopped \ No newline at end of file diff --git a/supervisord.conf b/supervisord.conf index d1650df..ad4782a 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -1,6 +1,7 @@ [supervisord] nodaemon=true -logfile=/var/log/supervisord.log +logfile=/dev/stdout +logfile_maxbytes=0 pidfile=/tmp/supervisord.pid [program:nginx] @@ -9,8 +10,8 @@ autorestart=true priority=10 [program:backend] -command=python start.py -directory=/app/backend +command=python -m backend.start +directory=/app autorestart=true priority=20 stdout_logfile=/dev/stdout From 1909816957d79938b9f22294768b8308c4d6968a Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 22:21:39 +0300 Subject: [PATCH 3/6] refactor(backend): rename files and names + added file kind postfix to backend files e.g. _api (public_api) + renamed classes (added same prefixes) + refactor imports (use more exact imports, removed unnecessary imports from __init__ files) --- backend/alembic/env.py | 5 +- backend/api/__init__.py | 12 +- backend/api/{admin.py => admin_api.py} | 86 +++++++----- backend/api/auth.py | 91 ------------ backend/api/auth_api.py | 114 +++++++++++++++ backend/api/{card.py => card_api.py} | 69 ++++++---- backend/api/password_recovery.py | 108 --------------- backend/api/password_recovery_api.py | 130 ++++++++++++++++++ backend/api/{public.py => public_api.py} | 23 ++-- backend/api/user.py | 71 ---------- backend/api/user_api.py | 90 ++++++++++++ backend/app.py | 2 +- backend/core/__init__.py | 3 - backend/core/{auth.py => auth_core.py} | 104 +++++++++----- backend/core/{smtp.py => smtp_core.py} | 0 backend/core/user.py | 21 --- backend/core/user_core.py | 34 +++++ backend/db/__init__.py | 9 -- backend/db/cleanup.py | 15 +- backend/db/models/__init__.py | 14 +- backend/db/models/{base.py => base_model.py} | 2 +- backend/db/models/{card.py => card_model.py} | 10 +- ...ode.py => password_recovery_code_model.py} | 12 +- ...efresh_token.py => refresh_token_model.py} | 10 +- .../models/{settings.py => settings_model.py} | 6 +- backend/db/models/user.py | 53 ------- backend/db/models/user_model.py | 75 ++++++++++ backend/db/models/user_role.py | 18 --- backend/db/models/user_role_model.py | 22 +++ backend/enums/__init__.py | 2 - .../enums/{settings.py => settings_enum.py} | 0 .../enums/{user_role.py => user_role_enum.py} | 0 backend/helpers/__init__.py | 3 - backend/helpers/get_setting_typed_value.py | 2 +- backend/schemas/__init__.py | 12 -- backend/schemas/{auth.py => auth_schema.py} | 6 +- backend/schemas/{card.py => card_schema.py} | 10 +- ...ecovery.py => password_recovery_schema.py} | 4 +- ..._settings.py => public_settings_schema.py} | 2 +- backend/schemas/settings.py | 17 --- backend/schemas/settings_schema.py | 13 ++ backend/schemas/{user.py => user_schema.py} | 16 ++- .../schemas/{version.py => version_schema.py} | 2 +- backend/test/api/test_admin.py | 34 +++-- backend/test/api/test_user.py | 63 ++++++--- 45 files changed, 786 insertions(+), 609 deletions(-) rename backend/api/{admin.py => admin_api.py} (54%) delete mode 100644 backend/api/auth.py create mode 100644 backend/api/auth_api.py rename backend/api/{card.py => card_api.py} (53%) delete mode 100644 backend/api/password_recovery.py create mode 100644 backend/api/password_recovery_api.py rename backend/api/{public.py => public_api.py} (72%) delete mode 100644 backend/api/user.py create mode 100644 backend/api/user_api.py rename backend/core/{auth.py => auth_core.py} (58%) rename backend/core/{smtp.py => smtp_core.py} (100%) delete mode 100644 backend/core/user.py create mode 100644 backend/core/user_core.py rename backend/db/models/{base.py => base_model.py} (67%) rename backend/db/models/{card.py => card_model.py} (86%) rename backend/db/models/{password_recovery_code.py => password_recovery_code_model.py} (78%) rename backend/db/models/{refresh_token.py => refresh_token_model.py} (81%) rename backend/db/models/{settings.py => settings_model.py} (86%) delete mode 100644 backend/db/models/user.py create mode 100644 backend/db/models/user_model.py delete mode 100644 backend/db/models/user_role.py create mode 100644 backend/db/models/user_role_model.py rename backend/enums/{settings.py => settings_enum.py} (100%) rename backend/enums/{user_role.py => user_role_enum.py} (100%) rename backend/schemas/{auth.py => auth_schema.py} (55%) rename backend/schemas/{card.py => card_schema.py} (89%) rename backend/schemas/{password_recovery.py => password_recovery_schema.py} (87%) rename backend/schemas/{public_settings.py => public_settings_schema.py} (74%) delete mode 100644 backend/schemas/settings.py create mode 100644 backend/schemas/settings_schema.py rename backend/schemas/{user.py => user_schema.py} (83%) rename backend/schemas/{version.py => version_schema.py} (80%) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index a802f20..40590fb 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,4 +1,5 @@ -from backend.db import Base, async_engine +from backend.db.models.base_model import BaseModel +from backend.db.session import async_engine from logging.config import fileConfig import asyncio from alembic import context @@ -16,7 +17,7 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -target_metadata = Base.metadata +target_metadata = BaseModel.metadata # other values from the config, defined by the needs of env.py, # can be acquired: diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 223fdf4..dad86a1 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -1,6 +1,6 @@ -from .auth import router as auth_router -from .user import router as user_router -from .card import router as card_router -from .password_recovery import router as password_recovery_router -from .public import router as public_router -from .admin import router as admin_router +from .auth_api import router as auth_router +from .user_api import router as user_router +from .card_api import router as card_router +from .password_recovery_api import router as password_recovery_router +from .public_api import router as public_router +from .admin_api import router as admin_router diff --git a/backend/api/admin.py b/backend/api/admin_api.py similarity index 54% rename from backend/api/admin.py rename to backend/api/admin_api.py index 074578f..ba4b348 100644 --- a/backend/api/admin.py +++ b/backend/api/admin_api.py @@ -1,25 +1,30 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select - -import backend.schemas as schemas, backend.enums as enums -from backend.db import models -from backend.core import auth -from backend.db import get_async_session -from backend.core.user import delete_user -from backend.core.smtp import EmailSender -from backend.helpers import get_setting_typed_value +from backend.enums.user_role_enum import EUserRole +from backend.schemas.settings_schema import ( + GetSettingsRequestItemSchema, + PatchSettingsRequestItemSchema, +) +from backend.schemas.user_schema import UserSchema +from backend.db.models.user_model import UserModel +from backend.db.models.settings_model import SettingModel +from backend.core.auth_core import is_user_admin, is_user_owner +from backend.db.session import get_async_session +from backend.core.user_core import delete_user +from backend.core.smtp_core import EmailSender +from backend.helpers.get_setting_typed_value import get_setting_typed_value router = APIRouter(tags=["admin"], prefix="/admin") -@router.get("/users", response_model=list[schemas.User]) +@router.get("/users", response_model=list[UserSchema]) async def get_users_list( session: AsyncSession = Depends(get_async_session), - _: models.User = Depends(auth.is_user_admin), + _: UserModel = Depends(is_user_admin), ): - stmt = select(models.User) + stmt = select(UserModel) result = await session.execute(stmt) return result.scalars().all() @@ -28,14 +33,15 @@ async def get_users_list( async def admin_delete_user( user_id: int, session: AsyncSession = Depends(get_async_session), - admin: models.User = Depends(auth.is_user_admin), + admin: UserModel = Depends(is_user_admin), ): if user_id == admin.id: raise HTTPException( - 403, "Admin cannot delete his own account with this function." + 403, + "Admin cannot delete his own account with this function.", ) - stmt = select(models.User).where(models.User.id == user_id).limit(1) + stmt = select(UserModel).where(UserModel.id == user_id).limit(1) result = await session.execute(stmt) user = result.scalar_one_or_none() @@ -43,8 +49,8 @@ async def admin_delete_user( raise HTTPException(404, "User not found") if ( - admin.role_code == enums.EUserRole.ADMIN - and user.role_code == enums.EUserRole.ADMIN + admin.role_code == EUserRole.ADMIN + and user.role_code == EUserRole.ADMIN ): raise HTTPException(403, "Admin cannot delete admin.") @@ -52,23 +58,26 @@ async def admin_delete_user( @router.put( - "/users/role", description="Change the user role. Only owner can change roles." + "/users/role", + description="Change the user role. Only owner can change roles.", ) async def owner_change_user_role( user_id: int, - role_code: enums.EUserRole, + role_code: EUserRole, session: AsyncSession = Depends(get_async_session), - owner: models.User = Depends(auth.is_user_owner), + owner: UserModel = Depends(is_user_owner), ): - owner_assign_error = HTTPException(403, "Owner role cannot be reassigned.") + owner_assign_error = HTTPException( + 403, "Owner role cannot be reassigned." + ) - if role_code == enums.EUserRole.OWNER: + if role_code == EUserRole.OWNER: raise owner_assign_error if user_id == owner.id: raise owner_assign_error - stmt = select(models.User).where(models.User.id == user_id).limit(1) + stmt = select(UserModel).where(UserModel.id == user_id).limit(1) result = await session.execute(stmt) user = result.scalar_one_or_none() @@ -81,12 +90,14 @@ async def owner_change_user_role( return {"detail": "User role has been changed"} -@router.get("/settings", response_model=schemas.GetSettingsRequest) +@router.get( + "/settings", response_model=list[GetSettingsRequestItemSchema] +) async def get_system_settings( session: AsyncSession = Depends(get_async_session), - _: models.User = Depends(auth.is_user_admin), + _: UserModel = Depends(is_user_admin), ): - stmt = select(models.Setting) + stmt = select(SettingModel) result = await session.execute(stmt) settings = result.scalars() res = [] @@ -106,19 +117,26 @@ async def get_system_settings( @router.patch("/settings") async def change_system_settings( - request: list[schemas.PatchSettingsRequestItem], + request: list[PatchSettingsRequestItemSchema], session: AsyncSession = Depends(get_async_session), - _: models.User = Depends(auth.is_user_admin), + _: UserModel = Depends(is_user_admin), ): for s in request: - stmt = select(models.Setting).where(models.Setting.key == s.key).limit(1) + stmt = ( + select(SettingModel) + .where(SettingModel.key == s.key) + .limit(1) + ) result = await session.execute(stmt) setting = result.scalar_one_or_none() if not setting: - raise HTTPException(status_code=404, detail=f"Setting '{s.key}' not found") + raise HTTPException( + status_code=404, detail=f"Setting '{s.key}' not found" + ) if setting.value_type != type(s.value).__name__: raise HTTPException( - 400, f"Invalid type of '{s.key}', expected '{setting.value_type}'" + 400, + f"Invalid type of '{s.key}', expected '{setting.value_type}'", ) setting.value = str(s.value) @@ -128,12 +146,16 @@ async def change_system_settings( @router.get("/smtp/status", response_model=bool) -async def get_smtp_status(_: models.User = Depends(auth.is_user_admin)): +async def get_smtp_status( + _: UserModel = Depends(is_user_admin), +): return EmailSender.status() @router.post("/smtp/test") -async def send_test_email(admin: models.User = Depends(auth.is_user_admin)): +async def send_test_email( + admin: UserModel = Depends(is_user_admin), +): EmailSender.send_email( admin.email, "Test email from Cardholder PWA", diff --git a/backend/api/auth.py b/backend/api/auth.py deleted file mode 100644 index d832873..0000000 --- a/backend/api/auth.py +++ /dev/null @@ -1,91 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException, status, Request -import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth -from fastapi.security import OAuth2PasswordRequestForm -from backend.helpers import now -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select -from sqlalchemy.orm import selectinload -from backend.helpers import delay_to_minimum - -router = APIRouter(tags=["auth"]) - - -@router.post("/token", response_model=schemas.TokenResponse) -@delay_to_minimum(1) -async def login( - request: Request, - form_data: OAuth2PasswordRequestForm = Depends(), - session: AsyncSession = Depends(db.get_async_session), -): - user = await auth.authenticate_user(session, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token, access_exp = auth.create_access_token({"sub": user.username}) - refresh_token = await auth.create_refresh_token( - user.id, - session, - request.headers.get("user-agent"), - request.client.host if request.client else None, - ) - return auth.build_token_response(access_token, access_exp, refresh_token) - - -@router.post("/token/refresh", response_model=schemas.TokenResponse) -async def refresh_token( - request: Request, - form: schemas.RefreshRequest, - session: AsyncSession = Depends(db.get_async_session), -): - stmt = ( - select(models.RefreshToken) - .where( - models.RefreshToken.token == form.refresh_token, - models.RefreshToken.revoked == False, - models.RefreshToken.expires_at > now(), - ) - .options(selectinload(models.RefreshToken.user)) - .limit(1) - ) - result = await session.execute(stmt) - db_token = result.scalar_one_or_none() - if not db_token: - raise HTTPException(status_code=401, detail="Invalid or expired refresh token") - - db_token.revoked = True - await session.commit() - - user = db_token.user - access_token, access_exp = auth.create_access_token({"sub": user.username}) - new_refresh = await auth.create_refresh_token( - user.id, - session, - request.headers.get("user-agent"), - request.client.host if request.client else None, - ) - return auth.build_token_response(access_token, access_exp, new_refresh) - - -@router.post("/logout") -async def logout( - form: schemas.RevokeRequest, - session: AsyncSession = Depends(db.get_async_session), - _=Depends(auth.is_user), -): - stmt = ( - select(models.RefreshToken) - .where( - models.RefreshToken.token == form.refresh_token, - models.RefreshToken.revoked == False, - ) - .limit(1) - ) - result = await session.execute(stmt) - db_token = result.scalar_one_or_none() - if db_token: - db_token.revoked = True - await session.commit() - return {"detail": "Logged out successfully."} diff --git a/backend/api/auth_api.py b/backend/api/auth_api.py new file mode 100644 index 0000000..325eb31 --- /dev/null +++ b/backend/api/auth_api.py @@ -0,0 +1,114 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Request +from backend.core.auth_core import ( + create_access_token, + create_refresh_token, + authenticate_user, + build_token_response, + is_user, +) +from backend.db.session import get_async_session +from backend.schemas.auth_schema import ( + TokenResponseSchema, + RefreshRequestSchema, + RevokeRequestSchema, +) +from backend.db.models.refresh_token_model import RefreshTokenModel +from fastapi.security import OAuth2PasswordRequestForm +from backend.helpers.now import now +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from backend.helpers.delay_to_minimum import delay_to_minimum + +router = APIRouter(tags=["auth"]) + + +@router.post("/token", response_model=TokenResponseSchema) +@delay_to_minimum(1) +async def login( + request: Request, + form_data: OAuth2PasswordRequestForm = Depends(), + session: AsyncSession = Depends(get_async_session), +): + user = await authenticate_user( + session, form_data.username, form_data.password + ) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token, access_exp = create_access_token( + {"sub": user.username} + ) + refresh_token = await create_refresh_token( + user.id, + session, + request.headers.get("user-agent"), + request.client.host if request.client else None, + ) + return build_token_response( + access_token, access_exp, refresh_token + ) + + +@router.post("/token/refresh", response_model=TokenResponseSchema) +async def refresh_token( + request: Request, + form: RefreshRequestSchema, + session: AsyncSession = Depends(get_async_session), +): + stmt = ( + select(RefreshTokenModel) + .where( + RefreshTokenModel.token == form.refresh_token, + RefreshTokenModel.revoked == False, + RefreshTokenModel.expires_at > now(), + ) + .options(selectinload(RefreshTokenModel.user)) + .limit(1) + ) + result = await session.execute(stmt) + db_token = result.scalar_one_or_none() + if not db_token: + raise HTTPException( + status_code=401, detail="Invalid or expired refresh token" + ) + + db_token.revoked = True + await session.commit() + + user = db_token.user + access_token, access_exp = create_access_token( + {"sub": user.username} + ) + new_refresh = await create_refresh_token( + user.id, + session, + request.headers.get("user-agent"), + request.client.host if request.client else None, + ) + return build_token_response(access_token, access_exp, new_refresh) + + +@router.post("/logout") +async def logout( + form: RevokeRequestSchema, + session: AsyncSession = Depends(get_async_session), + _=Depends(is_user), +): + stmt = ( + select(RefreshTokenModel) + .where( + RefreshTokenModel.token == form.refresh_token, + RefreshTokenModel.revoked == False, + ) + .limit(1) + ) + result = await session.execute(stmt) + db_token = result.scalar_one_or_none() + if db_token: + db_token.revoked = True + await session.commit() + return {"detail": "Logged out successfully."} diff --git a/backend/api/card.py b/backend/api/card_api.py similarity index 53% rename from backend/api/card.py rename to backend/api/card_api.py index b76bf18..7c5fb6f 100644 --- a/backend/api/card.py +++ b/backend/api/card_api.py @@ -1,30 +1,39 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -import backend.db as db, backend.db.models as models, backend.schemas as schemas, backend.core.auth as auth +from backend.schemas.card_schema import ( + CardSchema, + CardCreateSchema, + CardUpdateSchema, + CardPatchSchema, +) +from backend.db.models.user_model import UserModel +from backend.db.models.card_model import CardModel +from backend.db.session import get_async_session +from backend.core.auth_core import is_user from sqlalchemy import select router = APIRouter(tags=["card"]) -@router.get("/cards", response_model=list[schemas.Card]) +@router.get("/cards", response_model=list[CardSchema]) async def get_cards( - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): - stmt = select(models.Card).where(models.Card.user_id == user.id) + stmt = select(CardModel).where(CardModel.user_id == user.id) result = await session.execute(stmt) return result.scalars().all() -@router.get("/cards/{card_id}", response_model=schemas.Card) +@router.get("/cards/{card_id}", response_model=CardSchema) async def get_card( card_id: int, - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): stmt = ( - select(models.Card) - .where(models.Card.id == card_id, models.Card.user_id == user.id) + select(CardModel) + .where(CardModel.id == card_id, CardModel.user_id == user.id) .limit(1) ) result = await session.execute(stmt) @@ -34,29 +43,29 @@ async def get_card( return card -@router.post("/cards", response_model=schemas.Card, status_code=201) +@router.post("/cards", response_model=CardSchema, status_code=201) async def create_card( - card: schemas.CardCreate, - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + card: CardCreateSchema, + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): - new_card = models.Card(**card.model_dump(), user_id=user.id) + new_card = CardModel(**card.model_dump(), user_id=user.id) session.add(new_card) await session.commit() await session.refresh(new_card) return new_card -@router.put("/cards/{card_id}", response_model=schemas.Card) +@router.put("/cards/{card_id}", response_model=CardSchema) async def update_card( card_id: int, - updated: schemas.CardUpdate, - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + updated: CardUpdateSchema, + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): stmt = ( - select(models.Card) - .where(models.Card.id == card_id, models.Card.user_id == user.id) + select(CardModel) + .where(CardModel.id == card_id, CardModel.user_id == user.id) .limit(1) ) result = await session.execute(stmt) @@ -75,12 +84,12 @@ async def update_card( @router.delete("/cards/{card_id}") async def delete_card( card_id: int, - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): stmt = ( - select(models.Card) - .where(models.Card.id == card_id, models.Card.user_id == user.id) + select(CardModel) + .where(CardModel.id == card_id, CardModel.user_id == user.id) .limit(1) ) result = await session.execute(stmt) @@ -95,13 +104,13 @@ async def delete_card( @router.patch("/cards/{card_id}") async def patch_card( card_id: int, - body: schemas.CardPatch, - session: AsyncSession = Depends(db.get_async_session), - user: models.User = Depends(auth.is_user), + body: CardPatchSchema, + session: AsyncSession = Depends(get_async_session), + user: UserModel = Depends(is_user), ): stmt = ( - select(models.Card) - .where(models.Card.id == card_id, models.Card.user_id == user.id) + select(CardModel) + .where(CardModel.id == card_id, CardModel.user_id == user.id) .limit(1) ) result = await session.execute(stmt) diff --git a/backend/api/password_recovery.py b/backend/api/password_recovery.py deleted file mode 100644 index 3b2b330..0000000 --- a/backend/api/password_recovery.py +++ /dev/null @@ -1,108 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException, Request -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import update -import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth -import secrets -from datetime import timedelta -from backend.helpers import now -from backend.core.smtp import EmailSender -from backend.config import Config -from sqlalchemy import select, desc -from sqlalchemy.orm import selectinload -import asyncio -from backend.helpers import delay_to_minimum - -router = APIRouter(tags=["password recovery"], prefix="/recovery") - - -@router.post("/code", description="Request password recovery code") -@delay_to_minimum(1) -async def code( - request: Request, - body: schemas.PasswordRecoveryCodeBody, - session: AsyncSession = Depends(db.get_async_session), -): - stmt = select(models.User).where(models.User.email == body.email).limit(1) - result = await session.execute(stmt) - - user = result.scalar_one_or_none() - if user: - recent_code_stmt = ( - select(models.PasswordRecoveryCode) - .where( - models.PasswordRecoveryCode.user_id == user.id, - models.PasswordRecoveryCode.created_at > now() - timedelta(minutes=1), - ) - .order_by(desc(models.PasswordRecoveryCode.created_at)) - .limit(1) - ) - code_result = await session.execute(recent_code_stmt) - last_code = code_result.scalar_one_or_none() - if last_code: - return {} - - code = secrets.token_urlsafe(8) - expires_at = now() + timedelta( - minutes=Config.PASSWORD_RECOVERY_CODE_LIFETIME_MIN - ) - - reset_url = None - host = request.headers.get("host", "") - scheme = request.scope["scheme"] - if host and scheme: - reset_url = f"{scheme}://{host}/password-recovery/submit?code={code}" - asyncio.create_task( - asyncio.to_thread( - EmailSender.send_password_reset_email, body.email, code, reset_url - ) - ) - - db_code = models.PasswordRecoveryCode( - code=code, expires_at=expires_at, user_id=user.id - ) - session.add(db_code) - await session.commit() - await session.refresh(db_code) - return {} - - -@router.put("/submit", description="Submit password recovery with secret code") -async def password( - body: schemas.PasswordRecoverySubmitBody, - session: AsyncSession = Depends(db.get_async_session), -): - stmt = ( - select(models.PasswordRecoveryCode) - .where( - models.PasswordRecoveryCode.code == body.code, - models.PasswordRecoveryCode.revoked == False, - models.PasswordRecoveryCode.expires_at > now(), - ) - .options(selectinload(models.PasswordRecoveryCode.user)) - .limit(1) - ) - result = await session.execute(stmt) - db_code = result.scalar_one_or_none() - if not db_code: - raise HTTPException(status_code=401, detail="Invalid or expired code") - - user = db_code.user - user.hashed_password = auth.get_password_hash(body.password) - await session.commit() - await session.refresh(user) - - await session.execute( - update(models.PasswordRecoveryCode) - .where(models.PasswordRecoveryCode.user_id == user.id) - .values(revoked=True) - ) - await session.commit() - - await session.execute( - update(models.RefreshToken) - .where(models.RefreshToken.user_id == user.id) - .values(revoked=True) - ) - await session.commit() - - return {} diff --git a/backend/api/password_recovery_api.py b/backend/api/password_recovery_api.py new file mode 100644 index 0000000..64d8db0 --- /dev/null +++ b/backend/api/password_recovery_api.py @@ -0,0 +1,130 @@ +from fastapi import APIRouter, Depends, HTTPException, Request +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import update +from backend.schemas.password_recovery_schema import ( + PasswordRecoveryCodeRequestSchema, + PasswordRecoverySubmitSchema, +) +from backend.db.session import get_async_session +from backend.db.models.user_model import UserModel +from backend.db.models.password_recovery_code_model import ( + PasswordRecoveryCodeModel, +) +from backend.db.models.refresh_token_model import RefreshTokenModel +from backend.core.auth_core import get_password_hash +import secrets +from datetime import timedelta +from backend.helpers.now import now +from backend.core.smtp_core import EmailSender +from backend.config import Config +from sqlalchemy import select, desc +from sqlalchemy.orm import selectinload +import asyncio +from backend.helpers.delay_to_minimum import delay_to_minimum + +router = APIRouter(tags=["password recovery"], prefix="/recovery") + + +@router.post("/code", description="Request password recovery code") +@delay_to_minimum(1) +async def code( + request: Request, + body: PasswordRecoveryCodeRequestSchema, + session: AsyncSession = Depends(get_async_session), +): + stmt = ( + select(UserModel) + .where(UserModel.email == body.email) + .limit(1) + ) + result = await session.execute(stmt) + + user = result.scalar_one_or_none() + if user: + recent_code_stmt = ( + select(PasswordRecoveryCodeModel) + .where( + PasswordRecoveryCodeModel.user_id == user.id, + PasswordRecoveryCodeModel.created_at + > now() - timedelta(minutes=1), + ) + .order_by(desc(PasswordRecoveryCodeModel.created_at)) + .limit(1) + ) + code_result = await session.execute(recent_code_stmt) + last_code = code_result.scalar_one_or_none() + if last_code: + return {} + + code = secrets.token_urlsafe(8) + expires_at = now() + timedelta( + minutes=Config.PASSWORD_RECOVERY_CODE_LIFETIME_MIN + ) + + reset_url = None + host = request.headers.get("host", "") + scheme = request.scope["scheme"] + if host and scheme: + reset_url = f"{scheme}://{host}/password-recovery/submit?code={code}" + asyncio.create_task( + asyncio.to_thread( + EmailSender.send_password_reset_email, + body.email, + code, + reset_url, + ) + ) + + db_code = PasswordRecoveryCodeModel( + code=code, expires_at=expires_at, user_id=user.id + ) + session.add(db_code) + await session.commit() + await session.refresh(db_code) + return {} + + +@router.put( + "/submit", description="Submit password recovery with secret code" +) +async def password( + body: PasswordRecoverySubmitSchema, + session: AsyncSession = Depends(get_async_session), +): + stmt = ( + select(PasswordRecoveryCodeModel) + .where( + PasswordRecoveryCodeModel.code == body.code, + PasswordRecoveryCodeModel.revoked == False, + PasswordRecoveryCodeModel.expires_at > now(), + ) + .options(selectinload(PasswordRecoveryCodeModel.user)) + .limit(1) + ) + result = await session.execute(stmt) + db_code = result.scalar_one_or_none() + if not db_code: + raise HTTPException( + status_code=401, detail="Invalid or expired code" + ) + + user = db_code.user + user.hashed_password = get_password_hash(body.password) + await session.commit() + await session.refresh(user) + + await session.execute( + update(PasswordRecoveryCodeModel) + .where(PasswordRecoveryCodeModel.user_id == user.id) + .values(revoked=True) + ) + await session.commit() + + await session.execute( + update(RefreshTokenModel) + .where(RefreshTokenModel.user_id == user.id) + .values(revoked=True) + ) + await session.commit() + + return {} diff --git a/backend/api/public.py b/backend/api/public_api.py similarity index 72% rename from backend/api/public.py rename to backend/api/public_api.py index af8c73b..cb91787 100644 --- a/backend/api/public.py +++ b/backend/api/public_api.py @@ -1,21 +1,22 @@ from fastapi import APIRouter, Depends, HTTPException -import os from sqlalchemy import text, select from sqlalchemy.ext.asyncio import AsyncSession from typing import Any - -from backend.db import get_async_session -from backend.db import models -import backend.schemas as schemas -from backend.enums import ESettingKey -from backend.helpers import get_setting_typed_value +from backend.db.session import get_async_session +from backend.db.models.settings_model import SettingModel +from backend.schemas.version_schema import VersionSchema +from backend.schemas.public_settings_schema import ( + PublicSettingsItemSchema, +) +from backend.enums.settings_enum import ESettingKey +from backend.helpers.get_setting_typed_value import get_setting_typed_value from backend.config import Config router = APIRouter(tags=["public"], prefix="/public") -@router.get("/version", response_model=schemas.Version) +@router.get("/version", response_model=VersionSchema) def get_version(): try: with open("/app/version", "r") as file: @@ -35,7 +36,7 @@ async def health(session: AsyncSession = Depends(get_async_session)): @router.get( "/settings", - response_model=list[schemas.PublicSettingsItem], + response_model=list[PublicSettingsItemSchema], description="Get list of several app settings and environment variables", ) async def settings( @@ -44,8 +45,8 @@ async def settings( result_list: list[dict[str, Any]] = [] public_keys = [ESettingKey.ALLOW_REGISTRATION] - stmt = select(models.Setting).where( - models.Setting.key.in_(public_keys) + stmt = select(SettingModel).where( + SettingModel.key.in_(public_keys) ) result = await session.execute(stmt) settings = result.scalars() diff --git a/backend/api/user.py b/backend/api/user.py deleted file mode 100644 index 7530c2e..0000000 --- a/backend/api/user.py +++ /dev/null @@ -1,71 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession -import backend.db.models as models, backend.db as db, backend.schemas as schemas, backend.core.auth as auth, backend.enums as enums -from backend.core.user import delete_user as _delete_user -from sqlalchemy import select -from backend.helpers import delay_to_minimum - -router = APIRouter(tags=["user"]) - - -@router.post("/user", response_model=schemas.User, status_code=201) -@delay_to_minimum(1) -async def create_user( - user: schemas.UserCreate, - session: AsyncSession = Depends(db.get_async_session), - _=Depends(auth.allow_registration), -): - creds_taken = await auth.is_creds_taken(session, user.username, user.email, None) - if creds_taken: - raise HTTPException(400, "Username or email is already taken") - - stmt = select(models.User).limit(1) - result = await session.execute(stmt) - is_first = result.scalar_one_or_none() is None - - new_user = models.User( - username=user.username, - email=user.email, - hashed_password=auth.get_password_hash(user.password), - ) - if is_first: - new_user.role_code = enums.EUserRole.OWNER - session.add(new_user) - await session.commit() - await session.refresh(new_user) - return new_user - - -@router.get("/user", response_model=schemas.User) -async def get_user(current_user=Depends(auth.is_user)): - return current_user - - -@router.put("/user", response_model=schemas.User) -async def update_user( - data: schemas.UserUpdate, - session: AsyncSession = Depends(db.get_async_session), - current_user: models.User = Depends(auth.is_user), -): - creds_taken = await auth.is_creds_taken( - session, data.username, data.email, current_user.id - ) - if creds_taken: - raise HTTPException(400, detail="Username or email is already taken") - if data.username != current_user.username: - current_user.username = data.username - if data.email != current_user.email: - current_user.email = data.email - if data.password: - current_user.hashed_password = auth.get_password_hash(data.password) - await session.commit() - await session.refresh(current_user) - return current_user - - -@router.delete("/user") -async def delete_user( - session: AsyncSession = Depends(db.get_async_session), - current_user: models.User = Depends(auth.is_user), -): - return await _delete_user(session, current_user) diff --git a/backend/api/user_api.py b/backend/api/user_api.py new file mode 100644 index 0000000..dcdaf77 --- /dev/null +++ b/backend/api/user_api.py @@ -0,0 +1,90 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from backend.db.session import get_async_session +from backend.db.models.user_model import UserModel +from backend.core.auth_core import ( + allow_registration, + is_creds_taken, + get_password_hash, + is_user, +) +from backend.schemas.user_schema import ( + UserSchema, + UserCreateSchema, + UserUpdateSchema, +) +from backend.core.user_core import delete_user as _delete_user +from sqlalchemy import select +from backend.helpers.delay_to_minimum import delay_to_minimum +from backend.enums.user_role_enum import EUserRole + +router = APIRouter(tags=["user"]) + + +@router.post("/user", response_model=UserSchema, status_code=201) +@delay_to_minimum(1) +async def create_user( + user: UserCreateSchema, + session: AsyncSession = Depends(get_async_session), + _=Depends(allow_registration), +): + creds_taken = await is_creds_taken( + session, user.username, user.email, None + ) + if creds_taken: + raise HTTPException(400, "Username or email is already taken") + + stmt = select(UserModel).limit(1) + result = await session.execute(stmt) + is_first = result.scalar_one_or_none() is None + + new_user = UserModel( + username=user.username, + email=user.email, + hashed_password=get_password_hash(user.password), + ) + if is_first: + new_user.role_code = EUserRole.OWNER + session.add(new_user) + await session.commit() + await session.refresh(new_user) + return new_user + + +@router.get("/user", response_model=UserSchema) +async def get_user(current_user=Depends(is_user)): + return current_user + + +@router.put("/user", response_model=UserSchema) +async def update_user( + data: UserUpdateSchema, + session: AsyncSession = Depends(get_async_session), + current_user: UserModel = Depends(is_user), +): + creds_taken = await is_creds_taken( + session, data.username, data.email, current_user.id + ) + if creds_taken: + raise HTTPException( + 400, detail="Username or email is already taken" + ) + if data.username != current_user.username: + current_user.username = data.username + if data.email != current_user.email: + current_user.email = data.email + if data.password: + current_user.hashed_password = get_password_hash( + data.password + ) + await session.commit() + await session.refresh(current_user) + return current_user + + +@router.delete("/user") +async def delete_user( + session: AsyncSession = Depends(get_async_session), + current_user: UserModel = Depends(is_user), +): + return await _delete_user(session, current_user) diff --git a/backend/app.py b/backend/app.py index c192959..1c2d49d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -9,7 +9,7 @@ admin_router, ) import asyncio -from backend.db import cleanup +from backend.db.cleanup import cleanup from contextlib import asynccontextmanager diff --git a/backend/core/__init__.py b/backend/core/__init__.py index ee85a00..e69de29 100644 --- a/backend/core/__init__.py +++ b/backend/core/__init__.py @@ -1,3 +0,0 @@ -from .auth import * -from .smtp import EmailSender -from .user import * \ No newline at end of file diff --git a/backend/core/auth.py b/backend/core/auth_core.py similarity index 58% rename from backend/core/auth.py rename to backend/core/auth_core.py index 4a6520a..b7c0aa1 100644 --- a/backend/core/auth.py +++ b/backend/core/auth_core.py @@ -2,15 +2,20 @@ from fastapi.security import OAuth2PasswordBearer from jose import jwt, JWTError from datetime import timedelta, datetime -from backend.helpers import now +from backend.helpers.now import now import bcrypt -import backend.schemas as schemas, backend.db as db, backend.db.models as models, backend.enums as enums from typing import cast import secrets from backend.config import Config from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, or_ - +from backend.db.session import get_async_session +from backend.db.models.user_model import UserModel +from backend.db.models.refresh_token_model import RefreshTokenModel +from backend.db.models.settings_model import SettingModel +from backend.schemas.auth_schema import TokenResponseSchema +from backend.enums.user_role_enum import EUserRole +from backend.enums.settings_enum import ESettingKey oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") @@ -28,14 +33,19 @@ def get_password_hash(password: str) -> str: return hashed_password.decode("utf-8") -def create_access_token(data: dict, expires_delta: timedelta | None = None): +def create_access_token( + data: dict, expires_delta: timedelta | None = None +): to_encode = data.copy() expire = now() + ( - expires_delta or timedelta(minutes=Config.ACCESS_TOKEN_LIFETIME_MIN) + expires_delta + or timedelta(minutes=Config.ACCESS_TOKEN_LIFETIME_MIN) ) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode( - to_encode, Config.JWT_SECRET_KEY, algorithm=Config.JWT_ALGORITHM + to_encode, + Config.JWT_SECRET_KEY, + algorithm=Config.JWT_ALGORITHM, ) return encoded_jwt, expire @@ -47,8 +57,10 @@ async def create_refresh_token( ip: str | None = None, ): token = secrets.token_urlsafe(32) - expires_at = now() + timedelta(minutes=Config.REFRESH_TOKEN_LIFETIME_MIN) - db_token = models.RefreshToken( + expires_at = now() + timedelta( + minutes=Config.REFRESH_TOKEN_LIFETIME_MIN + ) + db_token = RefreshTokenModel( token=token, user_id=user_id, expires_at=expires_at, @@ -61,19 +73,29 @@ async def create_refresh_token( return db_token -async def authenticate_user(session: AsyncSession, username: str, password: str): - stmt = select(models.User).where(models.User.username == username).limit(1) +async def authenticate_user( + session: AsyncSession, username: str, password: str +): + stmt = ( + select(UserModel) + .where(UserModel.username == username) + .limit(1) + ) result = await session.execute(stmt) user = result.scalar_one_or_none() - if not user or not verify_password(password, user.hashed_password): + if not user or not verify_password( + password, user.hashed_password + ): return None return user def build_token_response( - access_token: str, access_exp: datetime, refresh_token: models.RefreshToken + access_token: str, + access_exp: datetime, + refresh_token: RefreshTokenModel, ): - return schemas.TokenResponse( + return TokenResponseSchema( access_token=access_token, token_type="bearer", expires_in=int((access_exp - now()).total_seconds()), @@ -83,24 +105,32 @@ def build_token_response( async def is_user( token: str = Depends(oauth2_scheme), - session: AsyncSession = Depends(db.get_async_session), -) -> models.User: + session: AsyncSession = Depends(get_async_session), +) -> UserModel: """ Check if the request credentials is valid. Returns user. Raise 401 on fail """ - credentials_exception = HTTPException(status_code=401, detail="Invalid token") + credentials_exception = HTTPException( + status_code=401, detail="Invalid token" + ) try: payload = jwt.decode( - token, Config.JWT_SECRET_KEY, algorithms=[Config.JWT_ALGORITHM] + token, + Config.JWT_SECRET_KEY, + algorithms=[Config.JWT_ALGORITHM], ) username: str = cast(str, payload.get("sub")) if not username: raise credentials_exception except JWTError: raise credentials_exception - stmt = select(models.User).where(models.User.username == username).limit(1) + stmt = ( + select(UserModel) + .where(UserModel.username == username) + .limit(1) + ) result = await session.execute(stmt) user = result.scalar_one_or_none() if not user: @@ -110,15 +140,15 @@ async def is_user( async def is_user_admin( token: str = Depends(oauth2_scheme), - session: AsyncSession = Depends(db.get_async_session), -) -> models.User: + session: AsyncSession = Depends(get_async_session), +) -> UserModel: """ Check if the request credentials is valid and match admin/owner. Returns user. Raise 403 on fail. """ user = await is_user(token, session) - roles: list[enums.EUserRole] = [enums.EUserRole.OWNER, enums.EUserRole.ADMIN] + roles: list[EUserRole] = [EUserRole.OWNER, EUserRole.ADMIN] if user.role_code in roles: return user raise HTTPException(403, "Not admin user") @@ -126,45 +156,55 @@ async def is_user_admin( async def is_user_owner( token: str = Depends(oauth2_scheme), - session: AsyncSession = Depends(db.get_async_session), -) -> models.User: + session: AsyncSession = Depends(get_async_session), +) -> UserModel: """ Check if the request credentials is valid and match owner. Returns user. Raise 403 on fail. """ user = await is_user(token, session) - if user.role_code == enums.EUserRole.OWNER: + if user.role_code == EUserRole.OWNER: return user raise HTTPException(403, "Not owner user") -async def allow_registration(session: AsyncSession = Depends(db.get_async_session)): +async def allow_registration( + session: AsyncSession = Depends(get_async_session), +): """Check if user registration is allowed. Raise 403 on fail.""" stmt = ( - select(models.Setting) - .where(models.Setting.key == enums.ESettingKey.ALLOW_REGISTRATION) + select(SettingModel) + .where(SettingModel.key == ESettingKey.ALLOW_REGISTRATION) .limit(1) ) result = await session.execute(stmt) setting = result.scalar_one_or_none() if not setting or setting.value.lower() != "true": - raise HTTPException(403, "registration feature disabled in app settings") + raise HTTPException( + 403, "registration feature disabled in app settings" + ) return True async def is_creds_taken( - session: AsyncSession, username: str, email: str, user_id: int | None + session: AsyncSession, + username: str, + email: str, + user_id: int | None, ): """ Check if user credentials already taken by another user. """ stmt = ( - select(models.User) + select(UserModel) .where( - or_(models.User.username == username, models.User.email == email), - models.User.id != user_id, + or_( + UserModel.username == username, + UserModel.email == email, + ), + UserModel.id != user_id, ) .limit(1) ) diff --git a/backend/core/smtp.py b/backend/core/smtp_core.py similarity index 100% rename from backend/core/smtp.py rename to backend/core/smtp_core.py diff --git a/backend/core/user.py b/backend/core/user.py deleted file mode 100644 index 76b80bd..0000000 --- a/backend/core/user.py +++ /dev/null @@ -1,21 +0,0 @@ -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import delete -from fastapi import HTTPException -from backend.db.models import User, Card, RefreshToken, PasswordRecoveryCode -import backend.enums as enums - - -async def delete_user( - db: AsyncSession, - user: User, -): - if user.role_code == enums.EUserRole.OWNER: - raise HTTPException(403, "Owner cannot be deleted") - await db.execute(delete(Card).where(Card.user_id == user.id)) - await db.execute(delete(RefreshToken).where(RefreshToken.user_id == user.id)) - await db.execute( - delete(PasswordRecoveryCode).where(PasswordRecoveryCode.user_id == user.id) - ) - await db.delete(user) - await db.commit() - return {"detail": "User and all related data deleted"} diff --git a/backend/core/user_core.py b/backend/core/user_core.py new file mode 100644 index 0000000..2f74e58 --- /dev/null +++ b/backend/core/user_core.py @@ -0,0 +1,34 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import delete +from fastapi import HTTPException +from backend.db.models.user_model import UserModel +from backend.db.models.card_model import CardModel +from backend.db.models.refresh_token_model import RefreshTokenModel +from backend.db.models.password_recovery_code_model import ( + PasswordRecoveryCodeModel, +) +from backend.enums.user_role_enum import EUserRole + + +async def delete_user( + db: AsyncSession, + user: UserModel, +): + if user.role_code == EUserRole.OWNER: + raise HTTPException(403, "Owner cannot be deleted") + await db.execute( + delete(CardModel).where(CardModel.user_id == user.id) + ) + await db.execute( + delete(RefreshTokenModel).where( + RefreshTokenModel.user_id == user.id + ) + ) + await db.execute( + delete(PasswordRecoveryCodeModel).where( + PasswordRecoveryCodeModel.user_id == user.id + ) + ) + await db.delete(user) + await db.commit() + return {"detail": "User and all related data deleted"} diff --git a/backend/db/__init__.py b/backend/db/__init__.py index 0fae924..e69de29 100644 --- a/backend/db/__init__.py +++ b/backend/db/__init__.py @@ -1,9 +0,0 @@ -from .models import Base -from .models import User -from .models import RefreshToken -from .models import Card -from .models import PasswordRecoveryCode -from .models import UserRole -from .models import Setting -from .session import get_async_session, async_engine -from .cleanup import cleanup diff --git a/backend/db/cleanup.py b/backend/db/cleanup.py index 66bc117..31faf33 100644 --- a/backend/db/cleanup.py +++ b/backend/db/cleanup.py @@ -1,11 +1,12 @@ import asyncio from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import delete -from .models import RefreshToken, PasswordRecoveryCode +from .models.refresh_token_model import RefreshTokenModel +from .models.password_recovery_code_model import PasswordRecoveryCodeModel from .session import _async_session_maker import logging from backend.config import Config -from backend.helpers import now +from backend.helpers.now import now async def cleanup(): @@ -24,14 +25,14 @@ async def _cleanup(session: AsyncSession): _now = now() await session.execute( - delete(RefreshToken).where( - (RefreshToken.expires_at < _now) | (RefreshToken.revoked == True) + delete(RefreshTokenModel).where( + (RefreshTokenModel.expires_at < _now) | (RefreshTokenModel.revoked == True) ) ) await session.execute( - delete(PasswordRecoveryCode).where( - (PasswordRecoveryCode.expires_at < _now) - | (PasswordRecoveryCode.revoked == True) + delete(PasswordRecoveryCodeModel).where( + (PasswordRecoveryCodeModel.expires_at < _now) + | (PasswordRecoveryCodeModel.revoked == True) ) ) diff --git a/backend/db/models/__init__.py b/backend/db/models/__init__.py index f086535..0928f28 100644 --- a/backend/db/models/__init__.py +++ b/backend/db/models/__init__.py @@ -1,7 +1,7 @@ -from .base import Base -from .user import User -from .refresh_token import RefreshToken -from .card import Card -from .password_recovery_code import PasswordRecoveryCode -from .user_role import UserRole -from .settings import Setting +from .base_model import BaseModel +from .user_model import UserModel +from .refresh_token_model import RefreshTokenModel +from .card_model import CardModel +from .password_recovery_code_model import PasswordRecoveryCodeModel +from .user_role_model import UserRoleModel +from .settings_model import SettingModel diff --git a/backend/db/models/base.py b/backend/db/models/base_model.py similarity index 67% rename from backend/db/models/base.py rename to backend/db/models/base_model.py index bf59cd1..d1de08a 100644 --- a/backend/db/models/base.py +++ b/backend/db/models/base_model.py @@ -1,4 +1,4 @@ from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.orm import declarative_base -Base: DeclarativeMeta = declarative_base() +BaseModel: DeclarativeMeta = declarative_base() diff --git a/backend/db/models/card.py b/backend/db/models/card_model.py similarity index 86% rename from backend/db/models/card.py rename to backend/db/models/card_model.py index 47446a7..2d3c2fe 100644 --- a/backend/db/models/card.py +++ b/backend/db/models/card_model.py @@ -1,5 +1,5 @@ -from backend.helpers import now -from .base import Base +from backend.helpers.now import now +from .base_model import BaseModel from sqlalchemy import Integer, String, Text, DateTime, ForeignKey, text, Boolean from sqlalchemy.orm import relationship, mapped_column, Mapped from sqlalchemy.sql import func @@ -7,10 +7,10 @@ from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from .user import User + from .user_model import UserModel -class Card(Base): +class CardModel(BaseModel): __tablename__ = "cards" id: Mapped[int] = mapped_column( Integer, primary_key=True, index=True, nullable=False @@ -39,4 +39,4 @@ class Card(Base): nullable=False, ) - user: Mapped["User"] = relationship("User", back_populates="cards") + user: Mapped["UserModel"] = relationship("UserModel", back_populates="cards") diff --git a/backend/db/models/password_recovery_code.py b/backend/db/models/password_recovery_code_model.py similarity index 78% rename from backend/db/models/password_recovery_code.py rename to backend/db/models/password_recovery_code_model.py index b513156..3ff4ca5 100644 --- a/backend/db/models/password_recovery_code.py +++ b/backend/db/models/password_recovery_code_model.py @@ -1,15 +1,15 @@ -from .base import Base +from .base_model import BaseModel from sqlalchemy import Integer, String, Boolean, DateTime, ForeignKey, text from sqlalchemy.orm import relationship, mapped_column, Mapped from datetime import datetime from typing import TYPE_CHECKING -from backend.helpers import now +from backend.helpers.now import now if TYPE_CHECKING: - from .user import User + from .user_model import UserModel -class PasswordRecoveryCode(Base): +class PasswordRecoveryCodeModel(BaseModel): """Model of table with password recovery codes""" __tablename__ = "password_recovery_codes" @@ -28,6 +28,6 @@ class PasswordRecoveryCode(Base): Boolean, server_default=text("FALSE"), default=False, nullable=False ) - user: Mapped["User"] = relationship( - "User", back_populates="password_recovery_codes" + user: Mapped["UserModel"] = relationship( + "UserModel", back_populates="password_recovery_codes" ) diff --git a/backend/db/models/refresh_token.py b/backend/db/models/refresh_token_model.py similarity index 81% rename from backend/db/models/refresh_token.py rename to backend/db/models/refresh_token_model.py index 06152fd..c9de9cf 100644 --- a/backend/db/models/refresh_token.py +++ b/backend/db/models/refresh_token_model.py @@ -1,15 +1,15 @@ -from .base import Base +from .base_model import BaseModel from sqlalchemy import Integer, String, Boolean, DateTime, ForeignKey, text from sqlalchemy.orm import relationship, mapped_column, Mapped from datetime import datetime from typing import TYPE_CHECKING, Optional -from backend.helpers import now +from backend.helpers.now import now if TYPE_CHECKING: - from .user import User + from .user_model import UserModel -class RefreshToken(Base): +class RefreshTokenModel(BaseModel): __tablename__ = "refresh_tokens" id: Mapped[int] = mapped_column( Integer, primary_key=True, index=True, nullable=False @@ -28,4 +28,4 @@ class RefreshToken(Base): user_agent: Mapped[Optional[str]] = mapped_column(String, nullable=True) ip_address: Mapped[Optional[str]] = mapped_column(String, nullable=True) - user: Mapped["User"] = relationship("User", back_populates="refresh_tokens") + user: Mapped["UserModel"] = relationship("UserModel", back_populates="refresh_tokens") diff --git a/backend/db/models/settings.py b/backend/db/models/settings_model.py similarity index 86% rename from backend/db/models/settings.py rename to backend/db/models/settings_model.py index bf0c1f9..b7ac005 100644 --- a/backend/db/models/settings.py +++ b/backend/db/models/settings_model.py @@ -1,11 +1,11 @@ -from .base import Base +from .base_model import BaseModel from sqlalchemy import String, DateTime, text from sqlalchemy.orm import mapped_column, Mapped from datetime import datetime -from backend.helpers import now +from backend.helpers.now import now -class Setting(Base): +class SettingModel(BaseModel): __tablename__ = "settings" key: Mapped[str] = mapped_column(String, primary_key=True, nullable=False) diff --git a/backend/db/models/user.py b/backend/db/models/user.py deleted file mode 100644 index 6a1f030..0000000 --- a/backend/db/models/user.py +++ /dev/null @@ -1,53 +0,0 @@ -from .base import Base -from sqlalchemy import ForeignKey, Integer, String, DateTime, text, Enum as SQLEnum -from sqlalchemy.orm import relationship, mapped_column, Mapped -from datetime import datetime -from typing import TYPE_CHECKING -from backend.helpers import now -from backend.enums import EUserRole - - -if TYPE_CHECKING: - from .card import Card - from .refresh_token import RefreshToken - from .password_recovery_code import PasswordRecoveryCode - from .user_role import UserRole - - -class User(Base): - __tablename__ = "users" - id: Mapped[int] = mapped_column( - Integer, primary_key=True, index=True, nullable=False - ) - username: Mapped[str] = mapped_column( - String, unique=True, index=True, nullable=False - ) - email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) - hashed_password: Mapped[str] = mapped_column(String, nullable=False) - created_at: Mapped[datetime] = mapped_column( - DateTime, server_default=text("CURRENT_TIMESTAMP"), default=now, nullable=False - ) - updated_at: Mapped[datetime] = mapped_column( - DateTime, - server_default=text("CURRENT_TIMESTAMP"), - server_onupdate=text("CURRENT_TIMESTAMP"), - onupdate=now, - default=now, - nullable=False, - ) - role_code: Mapped[EUserRole] = mapped_column( - SQLEnum(EUserRole, native_enum=False), - ForeignKey("user_roles.code"), - server_default=EUserRole.MEMBER, - default=EUserRole.MEMBER, - nullable=False, - ) - - cards: Mapped[list["Card"]] = relationship("Card", back_populates="user") - refresh_tokens: Mapped[list["RefreshToken"]] = relationship( - "RefreshToken", back_populates="user" - ) - password_recovery_codes: Mapped[list["PasswordRecoveryCode"]] = relationship( - "PasswordRecoveryCode", back_populates="user" - ) - role: Mapped["UserRole"] = relationship("UserRole", back_populates="users") diff --git a/backend/db/models/user_model.py b/backend/db/models/user_model.py new file mode 100644 index 0000000..51001c0 --- /dev/null +++ b/backend/db/models/user_model.py @@ -0,0 +1,75 @@ +from .base_model import BaseModel +from sqlalchemy import ( + ForeignKey, + Integer, + String, + DateTime, + text, + Enum as SQLEnum, +) +from sqlalchemy.orm import relationship, mapped_column, Mapped +from datetime import datetime +from typing import TYPE_CHECKING +from backend.helpers.now import now +from backend.enums.user_role_enum import EUserRole + + +if TYPE_CHECKING: + from .card_model import CardModel + from .refresh_token_model import RefreshTokenModel + from .password_recovery_code_model import ( + PasswordRecoveryCodeModel, + ) + from .user_role_model import UserRoleModel + + +class UserModel(BaseModel): + __tablename__ = "users" + id: Mapped[int] = mapped_column( + Integer, primary_key=True, index=True, nullable=False + ) + username: Mapped[str] = mapped_column( + String, unique=True, index=True, nullable=False + ) + email: Mapped[str] = mapped_column( + String, unique=True, index=True, nullable=False + ) + hashed_password: Mapped[str] = mapped_column( + String, nullable=False + ) + created_at: Mapped[datetime] = mapped_column( + DateTime, + server_default=text("CURRENT_TIMESTAMP"), + default=now, + nullable=False, + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime, + server_default=text("CURRENT_TIMESTAMP"), + server_onupdate=text("CURRENT_TIMESTAMP"), + onupdate=now, + default=now, + nullable=False, + ) + role_code: Mapped[EUserRole] = mapped_column( + SQLEnum(EUserRole, native_enum=False), + ForeignKey("user_roles.code"), + server_default=EUserRole.MEMBER, + default=EUserRole.MEMBER, + nullable=False, + ) + + cards: Mapped[list["CardModel"]] = relationship( + "CardModel", back_populates="user" + ) + refresh_tokens: Mapped[list["RefreshTokenModel"]] = relationship( + "RefreshTokenModel", back_populates="user" + ) + password_recovery_codes: Mapped[ + list["PasswordRecoveryCodeModel"] + ] = relationship( + "PasswordRecoveryCodeModel", back_populates="user" + ) + role: Mapped["UserRoleModel"] = relationship( + "UserRoleModel", back_populates="users" + ) diff --git a/backend/db/models/user_role.py b/backend/db/models/user_role.py deleted file mode 100644 index 7a98547..0000000 --- a/backend/db/models/user_role.py +++ /dev/null @@ -1,18 +0,0 @@ -from .base import Base -from sqlalchemy import Enum as SQLEnum -from sqlalchemy.orm import Mapped, mapped_column, relationship -from typing import TYPE_CHECKING -from backend.enums import EUserRole - - -if TYPE_CHECKING: - from .user import User - - -class UserRole(Base): - __tablename__ = "user_roles" - code: Mapped[EUserRole] = mapped_column( - SQLEnum(EUserRole, native_enum=False), primary_key=True, nullable=False - ) - - users: Mapped[list["User"]] = relationship("User", back_populates="role") diff --git a/backend/db/models/user_role_model.py b/backend/db/models/user_role_model.py new file mode 100644 index 0000000..7e93c34 --- /dev/null +++ b/backend/db/models/user_role_model.py @@ -0,0 +1,22 @@ +from .base_model import BaseModel +from sqlalchemy import Enum as SQLEnum +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import TYPE_CHECKING +from backend.enums.user_role_enum import EUserRole + + +if TYPE_CHECKING: + from .user_model import UserModel + + +class UserRoleModel(BaseModel): + __tablename__ = "user_roles" + code: Mapped[EUserRole] = mapped_column( + SQLEnum(EUserRole, native_enum=False), + primary_key=True, + nullable=False, + ) + + users: Mapped[list["UserModel"]] = relationship( + "UserModel", back_populates="role" + ) diff --git a/backend/enums/__init__.py b/backend/enums/__init__.py index 78c6c93..e69de29 100644 --- a/backend/enums/__init__.py +++ b/backend/enums/__init__.py @@ -1,2 +0,0 @@ -from .user_role import EUserRole -from .settings import ESettingKey, ESettingType diff --git a/backend/enums/settings.py b/backend/enums/settings_enum.py similarity index 100% rename from backend/enums/settings.py rename to backend/enums/settings_enum.py diff --git a/backend/enums/user_role.py b/backend/enums/user_role_enum.py similarity index 100% rename from backend/enums/user_role.py rename to backend/enums/user_role_enum.py diff --git a/backend/helpers/__init__.py b/backend/helpers/__init__.py index a3e0fbe..e69de29 100644 --- a/backend/helpers/__init__.py +++ b/backend/helpers/__init__.py @@ -1,3 +0,0 @@ -from .now import now -from .delay_to_minimum import delay_to_minimum -from .get_setting_typed_value import get_setting_typed_value \ No newline at end of file diff --git a/backend/helpers/get_setting_typed_value.py b/backend/helpers/get_setting_typed_value.py index a63f46b..82861d3 100644 --- a/backend/helpers/get_setting_typed_value.py +++ b/backend/helpers/get_setting_typed_value.py @@ -1,4 +1,4 @@ -from backend.enums import ESettingType +from backend.enums.settings_enum import ESettingType def get_setting_typed_value(value: str, value_type: str): diff --git a/backend/schemas/__init__.py b/backend/schemas/__init__.py index bedb670..e69de29 100644 --- a/backend/schemas/__init__.py +++ b/backend/schemas/__init__.py @@ -1,12 +0,0 @@ -from .user import User, UserCreate, UserUpdate -from .auth import TokenResponse, RefreshRequest, RevokeRequest -from .card import CardBase, CardCreate, CardUpdate, Card, CardPatch -from .password_recovery import PasswordRecoveryCodeBody, PasswordRecoverySubmitBody -from .version import Version -from .settings import ( - PatchSettingsRequestItem, - GetSettingsRequestItem, - GetSettingsRequest, -) -from .public_settings import PublicSettingsItem -from .validators import password_validator diff --git a/backend/schemas/auth.py b/backend/schemas/auth_schema.py similarity index 55% rename from backend/schemas/auth.py rename to backend/schemas/auth_schema.py index e32c310..e57df48 100644 --- a/backend/schemas/auth.py +++ b/backend/schemas/auth_schema.py @@ -1,16 +1,16 @@ from pydantic import BaseModel -class TokenResponse(BaseModel): +class TokenResponseSchema(BaseModel): access_token: str token_type: str expires_in: int refresh_token: str -class RefreshRequest(BaseModel): +class RefreshRequestSchema(BaseModel): refresh_token: str -class RevokeRequest(RefreshRequest): +class RevokeRequestSchema(RefreshRequestSchema): pass diff --git a/backend/schemas/card.py b/backend/schemas/card_schema.py similarity index 89% rename from backend/schemas/card.py rename to backend/schemas/card_schema.py index 887feda..9698a73 100644 --- a/backend/schemas/card.py +++ b/backend/schemas/card_schema.py @@ -4,7 +4,7 @@ import re -class CardBase(BaseModel): +class CardBaseSchema(BaseModel): code: str code_type: str name: str @@ -22,15 +22,15 @@ def validate_color(cls, v: str) -> str | None: return v -class CardCreate(CardBase): +class CardCreateSchema(CardBaseSchema): pass -class CardUpdate(CardBase): +class CardUpdateSchema(CardBaseSchema): pass -class CardPatch(BaseModel): +class CardPatchSchema(BaseModel): code: Optional[str] = None code_type: Optional[str] = None name: Optional[str] = None @@ -58,7 +58,7 @@ def validate_color(cls, v: str) -> str | None: return v -class Card(CardBase): +class CardSchema(CardBaseSchema): id: int used_at: Optional[datetime] created_at: datetime diff --git a/backend/schemas/password_recovery.py b/backend/schemas/password_recovery_schema.py similarity index 87% rename from backend/schemas/password_recovery.py rename to backend/schemas/password_recovery_schema.py index 9cee3ea..62453ed 100644 --- a/backend/schemas/password_recovery.py +++ b/backend/schemas/password_recovery_schema.py @@ -3,13 +3,13 @@ from .validators import password_validator -class PasswordRecoveryCodeBody(BaseModel): +class PasswordRecoveryCodeRequestSchema(BaseModel): """Password restoration code request schema""" email: EmailStr -class PasswordRecoverySubmitBody(BaseModel): +class PasswordRecoverySubmitSchema(BaseModel): """Password restoration submit request schema""" code: str diff --git a/backend/schemas/public_settings.py b/backend/schemas/public_settings_schema.py similarity index 74% rename from backend/schemas/public_settings.py rename to backend/schemas/public_settings_schema.py index 35ea9a2..40f6cc1 100644 --- a/backend/schemas/public_settings.py +++ b/backend/schemas/public_settings_schema.py @@ -2,6 +2,6 @@ from pydantic import BaseModel -class PublicSettingsItem(BaseModel): +class PublicSettingsItemSchema(BaseModel): key: str value: Union[bool, int, float, str, None] = None \ No newline at end of file diff --git a/backend/schemas/settings.py b/backend/schemas/settings.py deleted file mode 100644 index ad0f738..0000000 --- a/backend/schemas/settings.py +++ /dev/null @@ -1,17 +0,0 @@ -from pydantic import BaseModel, RootModel -from typing import Union -from datetime import datetime - - -class PatchSettingsRequestItem(BaseModel): - key: str - value: Union[bool, int, float, str] - - -class GetSettingsRequestItem(PatchSettingsRequestItem): - value_type: str - updated_at: datetime - - -class GetSettingsRequest(RootModel[list[GetSettingsRequestItem]]): - pass diff --git a/backend/schemas/settings_schema.py b/backend/schemas/settings_schema.py new file mode 100644 index 0000000..0e2d56f --- /dev/null +++ b/backend/schemas/settings_schema.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, RootModel +from typing import Union +from datetime import datetime + + +class PatchSettingsRequestItemSchema(BaseModel): + key: str + value: Union[bool, int, float, str] + + +class GetSettingsRequestItemSchema(PatchSettingsRequestItemSchema): + value_type: str + updated_at: datetime \ No newline at end of file diff --git a/backend/schemas/user.py b/backend/schemas/user_schema.py similarity index 83% rename from backend/schemas/user.py rename to backend/schemas/user_schema.py index eb6377f..2cf522c 100644 --- a/backend/schemas/user.py +++ b/backend/schemas/user_schema.py @@ -1,11 +1,17 @@ from typing import Optional, Self -from pydantic import BaseModel, EmailStr, field_validator, model_validator, ConfigDict +from pydantic import ( + BaseModel, + EmailStr, + field_validator, + model_validator, + ConfigDict, +) from .validators import password_validator -from backend.enums import EUserRole +from backend.enums.user_role_enum import EUserRole from datetime import datetime -class UserCreate(BaseModel): +class UserCreateSchema(BaseModel): """User create schema. Passwords required.""" username: str @@ -25,7 +31,7 @@ def check_passwords_match(self) -> Self: return self -class UserUpdate(BaseModel): +class UserUpdateSchema(BaseModel): """User update schema. Passwords optional""" username: str @@ -47,7 +53,7 @@ def check_passwords_match(self) -> Self: return self -class User(BaseModel): +class UserSchema(BaseModel): id: int username: str email: EmailStr diff --git a/backend/schemas/version.py b/backend/schemas/version_schema.py similarity index 80% rename from backend/schemas/version.py rename to backend/schemas/version_schema.py index c58251b..14e362d 100644 --- a/backend/schemas/version.py +++ b/backend/schemas/version_schema.py @@ -2,7 +2,7 @@ from typing import Optional -class Version(BaseModel): +class VersionSchema(BaseModel): """Versions schema""" image_version: Optional[str] = None diff --git a/backend/test/api/test_admin.py b/backend/test/api/test_admin.py index 48e6efe..c488348 100644 --- a/backend/test/api/test_admin.py +++ b/backend/test/api/test_admin.py @@ -4,15 +4,15 @@ from sqlalchemy.ext.asyncio import AsyncSession from fastapi import HTTPException from typing import cast, Any -from backend.api.admin import admin_delete_user -from backend.db.models import User -from backend.enums import EUserRole +from backend.api.admin_api import admin_delete_user +from backend.db.models.user_model import UserModel +from backend.enums.user_role_enum import EUserRole @pytest.mark.asyncio async def test_admin__should_delete_user(): - admin = User(id=1, role_code=EUserRole.ADMIN) - user_to_delete = User(id=2, role_code=EUserRole.MEMBER) + admin = UserModel(id=1, role_code=EUserRole.ADMIN) + user_to_delete = UserModel(id=2, role_code=EUserRole.MEMBER) session_mock = AsyncMock(spec=AsyncSession) result_mock = MagicMock() @@ -27,20 +27,24 @@ async def test_admin__should_delete_user(): @pytest.mark.asyncio async def test_admin__should_not_delete_self(): - admin = User(id=1, role_code=EUserRole.ADMIN) + admin = UserModel(id=1, role_code=EUserRole.ADMIN) session_mock = AsyncMock() with pytest.raises(HTTPException) as exc_info: - await admin_delete_user(user_id=1, session=session_mock, admin=admin) + await admin_delete_user( + user_id=1, session=session_mock, admin=admin + ) assert exc_info.value.status_code == 403 - assert "cannot delete his own account" in str(exc_info.value.detail) + assert "cannot delete his own account" in str( + exc_info.value.detail + ) @pytest.mark.asyncio async def test_admin__user_not_found(): - admin = User(id=1, role_code=EUserRole.ADMIN) + admin = UserModel(id=1, role_code=EUserRole.ADMIN) session_mock = AsyncMock(spec=AsyncSession) result_mock = MagicMock() @@ -48,15 +52,17 @@ async def test_admin__user_not_found(): session_mock.execute.return_value = result_mock with pytest.raises(HTTPException) as exc_info: - await admin_delete_user(user_id=999, session=session_mock, admin=admin) + await admin_delete_user( + user_id=999, session=session_mock, admin=admin + ) assert exc_info.value.status_code == 404 @pytest.mark.asyncio async def test_admin__should_not_delete_other_admin(): - admin = User(id=1, role_code=EUserRole.ADMIN) - another_admin = User(id=2, role_code=EUserRole.ADMIN) + admin = UserModel(id=1, role_code=EUserRole.ADMIN) + another_admin = UserModel(id=2, role_code=EUserRole.ADMIN) session_mock = AsyncMock(spec=AsyncSession) result_mock = MagicMock() @@ -64,7 +70,9 @@ async def test_admin__should_not_delete_other_admin(): session_mock.execute.return_value = result_mock with pytest.raises(HTTPException) as exc_info: - await admin_delete_user(user_id=2, session=session_mock, admin=admin) + await admin_delete_user( + user_id=2, session=session_mock, admin=admin + ) assert exc_info.value.status_code == 403 assert "Admin cannot delete admin" in str(exc_info.value.detail) diff --git a/backend/test/api/test_user.py b/backend/test/api/test_user.py index 9ef9e03..946dc73 100644 --- a/backend/test/api/test_user.py +++ b/backend/test/api/test_user.py @@ -3,11 +3,14 @@ from unittest.mock import AsyncMock, MagicMock, patch from sqlalchemy.ext.asyncio import AsyncSession from fastapi import HTTPException -from backend.api.user import create_user, update_user -from backend.core.auth import verify_password -from backend.db.models import User -from backend.schemas import UserCreate, UserUpdate -from backend.enums import EUserRole +from backend.api.user_api import create_user, update_user +from backend.core.auth_core import verify_password +from backend.db.models.user_model import UserModel +from backend.schemas.user_schema import ( + UserCreateSchema, + UserUpdateSchema, +) +from backend.enums.user_role_enum import EUserRole def _get_user_create( @@ -17,8 +20,8 @@ def _get_user_create( "password": "123456qQ", "confirm_password": "123456qQ", } -) -> UserCreate: - return UserCreate(**payload) +) -> UserCreateSchema: + return UserCreateSchema(**payload) def _get_user_update( @@ -26,8 +29,8 @@ def _get_user_update( "username": "user_name", "email": "user_email@example.com", } -) -> UserUpdate: - return UserUpdate(**payload) +) -> UserUpdateSchema: + return UserUpdateSchema(**payload) @pytest.mark.asyncio @@ -41,13 +44,17 @@ async def test_user_should_create_owner(): session_mock.add.return_value = None with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await create_user(user, session_mock, True) assert result.role_code == EUserRole.OWNER - assert session_mock.add.mock_calls[0].args[0].role_code == EUserRole.OWNER + assert ( + session_mock.add.mock_calls[0].args[0].role_code + == EUserRole.OWNER + ) @pytest.mark.asyncio @@ -61,13 +68,17 @@ async def test_user_should_create_member(): session_mock.add.return_value = None with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await create_user(user, session_mock, True) assert result.role_code != EUserRole.OWNER - assert session_mock.add.mock_calls[0].args[0].role_code != EUserRole.OWNER + assert ( + session_mock.add.mock_calls[0].args[0].role_code + != EUserRole.OWNER + ) @pytest.mark.asyncio @@ -77,7 +88,8 @@ async def test_user_should_not_create_if_creds_taken(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -85,18 +97,21 @@ async def test_user_should_not_create_if_creds_taken(): await create_user(user, session_mock, True) assert exc_info.value.status_code == 400 - assert exc_info.value.detail == "Username or email is already taken" + assert ( + exc_info.value.detail == "Username or email is already taken" + ) @pytest.mark.asyncio async def test_user_should_not_update_if_creds_taken(): user = _get_user_update() - current_user: User = User(id=1) + current_user: UserModel = UserModel(id=1) session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -104,18 +119,21 @@ async def test_user_should_not_update_if_creds_taken(): await update_user(user, session_mock, current_user) assert exc_info.value.status_code == 400 - assert exc_info.value.detail == "Username or email is already taken" + assert ( + exc_info.value.detail == "Username or email is already taken" + ) @pytest.mark.asyncio async def test_user_should_update_with_no_password(): user = _get_user_update() - current_user: User = User(id=1) + current_user: UserModel = UserModel(id=1) session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await update_user(user, session_mock, current_user) @@ -134,12 +152,13 @@ async def test_user_should_update_with_password(): "confirm_password": "123456qQ", } ) - current_user: User = User(id=1) + current_user: UserModel = UserModel(id=1) session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth.is_creds_taken", new_callable=AsyncMock + "backend.core.auth_core.is_creds_taken", + new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False result = await update_user(user, session_mock, current_user) From 7c4c00b13eec4bd373ef75a556393a8723ff224b Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 22:33:32 +0300 Subject: [PATCH 4/6] chore: fix backend tests --- backend/test/api/test_user.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/test/api/test_user.py b/backend/test/api/test_user.py index 946dc73..8a8b0f6 100644 --- a/backend/test/api/test_user.py +++ b/backend/test/api/test_user.py @@ -44,7 +44,7 @@ async def test_user_should_create_owner(): session_mock.add.return_value = None with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False @@ -68,7 +68,7 @@ async def test_user_should_create_member(): session_mock.add.return_value = None with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False @@ -88,7 +88,7 @@ async def test_user_should_not_create_if_creds_taken(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -110,7 +110,7 @@ async def test_user_should_not_update_if_creds_taken(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = True @@ -132,7 +132,7 @@ async def test_user_should_update_with_no_password(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False @@ -157,7 +157,7 @@ async def test_user_should_update_with_password(): session_mock = AsyncMock(spec=AsyncSession) with patch( - "backend.core.auth_core.is_creds_taken", + "backend.api.user_api.is_creds_taken", new_callable=AsyncMock, ) as is_creds_taken_mock: is_creds_taken_mock.return_value = False From 1cf5f89f2af2282adbf80c0be11620ea9c4c5fca Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 22:54:38 +0300 Subject: [PATCH 5/6] feat(ci): multi-platform build + added multi-platform build with buildx --- .github/workflows/docker-image.yml | 97 +++++++++----------------- .github/workflows/semantic-release.yml | 44 ++++++++---- 2 files changed, 63 insertions(+), 78 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f455bba..5bc01ff 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,84 +1,51 @@ -name: Build and Push Docker Image on Version Tag +name: Build and push docker image with arbitrary tag on: - # push: - # tags: - # - 'v[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: inputs: - version: - description: "Tag version to build (e.g. v1.2.3), this will be the docker-image tag" + tag: + description: 'Tag of the image being built (e.g. dev). Cannot start with "v" or be "latest".' required: true - # workflow_run: - # workflows: ["Semantic Release"] - # types: - # - completed jobs: - check-tag: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - exists: ${{ steps.check_tag.outputs.exists }} - env: - IMAGE_NAME: quenary/cardholder_pwa - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Determine version - id: version - run: | - VERSION="${{ github.event.inputs.version }}" - - if [ -z "$VERSION" ]; then - echo "❌ ERROR: No version tag found. Aborting." - exit 1 - fi - - echo "✅ Using VERSION=$VERSION" - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Check if Docker tag already exists - id: check_tag - run: | - STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ - https://hub.docker.com/v2/repositories/${{ env.IMAGE_NAME }}/tags/${{ steps.version.outputs.version }}/) - echo "Status: $STATUS" - if [ "$STATUS" = "200" ]; then - echo "exists=true" >> $GITHUB_OUTPUT - else - echo "exists=false" >> $GITHUB_OUTPUT - fi - build: - needs: check-tag - if: needs.check-tag.outputs.version != '' && needs.check-tag.outputs.exists == 'false' + if: github.event.inputs.tag != 'latest' && !startsWith(github.event.inputs.tag, 'v') runs-on: ubuntu-latest env: IMAGE_NAME: quenary/cardholder_pwa - VERSION: ${{ needs.check-tag.outputs.version }} + TAG: ${{ github.event.inputs.tag }} steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Log in to Docker Hub - run: echo "${{ secrets.DOCKER_TOKEN }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Build and tag Docker image - run: | - docker build \ - --build-arg VERSION=${{ env.VERSION }} \ - -t $IMAGE_NAME:${{ env.VERSION }} \ - -t $IMAGE_NAME:latest . + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Push Docker image (${{ env.VERSION }}) - run: docker push $IMAGE_NAME:${{ env.VERSION }} + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} - - name: Push Docker image (latest) - run: docker push $IMAGE_NAME:latest + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: | + ${{ env.IMAGE_NAME }}:${{ env.TAG }} + build-args: | + VERSION=${{ env.TAG }} + labels: | + org.opencontainers.image.title="Cardholder PWA" + org.opencontainers.image.description="An app for your loyalty and discount cards." + org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }} + org.opencontainers.image.created=${{ github.run_started_at }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.licenses=MIT \ No newline at end of file diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 36ca5ee..c3a4a3b 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -47,7 +47,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.13 - name: Install backend dependencies run: | @@ -148,24 +148,42 @@ jobs: with: fetch-depth: 0 + # Semantic-release pushes new tag and modifies some files - name: Fetch latest refs explicitly run: | git fetch origin main git fetch --tags git reset --hard origin/main - - name: Log in to Docker Hub - run: echo "${{ secrets.DOCKER_TOKEN }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Build and tag Docker image - run: | - docker build \ - --build-arg VERSION=${{ env.VERSION }} \ - -t $IMAGE_NAME:${{ env.VERSION }} \ - -t $IMAGE_NAME:latest . + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Push Docker image (${{ env.VERSION }}) - run: docker push $IMAGE_NAME:${{ env.VERSION }} + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} - - name: Push Docker image (latest) - run: docker push $IMAGE_NAME:latest \ No newline at end of file + - name: Build and push full image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: | + ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.IMAGE_NAME }}:latest + build-args: | + VERSION=${{ env.VERSION }} + labels: | + org.opencontainers.image.title="Cardholder PWA" + org.opencontainers.image.description="An app for your loyalty and discount cards." + org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ env.VERSION }} + org.opencontainers.image.version=${{ env.VERSION }} + org.opencontainers.image.created=${{ github.run_started_at }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.licenses=MIT \ No newline at end of file From 8a4d1870c3d7b1eb10fb117a06ac518fc3a87e39 Mon Sep 17 00:00:00 2001 From: Quenary Date: Sat, 8 Nov 2025 23:44:15 +0300 Subject: [PATCH 6/6] chore(deps): updated dependencies + update dependencies of backend + update dependencies of frontend + edited backend/readme --- backend/readme.md | 13 +- backend/requirements.txt | 71 +- frontend/package-lock.json | 2256 ++++++++++++++++++------------------ frontend/package.json | 22 +- 4 files changed, 1155 insertions(+), 1207 deletions(-) diff --git a/backend/readme.md b/backend/readme.md index 658824c..0a22312 100644 --- a/backend/readme.md +++ b/backend/readme.md @@ -7,11 +7,14 @@ You may also need following packages `pip3 install python-is-python3 python3-dev ### Run the app -- create .env file (next to that readme, in /backend directory) with at least this variable `DB_URL=sqlite+aiosqlite:///./cardholder_pwa.db` -- create venv `python -m venv venv` -- activate with `source venv/bin/activate` or `venv/scripts/activate` -- install deps `pip install -r requirements.txt` -- run app with ```python dev.py``` or ```python start.py``` +- Run install.sh script to prepare python environment or do it manually +- In the root of the workspace, create an .env file with at least these variables + +```bash +DB_URL=sqlite+aiosqlite:///./cardholder_pwa.db +``` + +- Run the app with `python -m backend.dev` or `python -m backend.start` ### Migrations diff --git a/backend/requirements.txt b/backend/requirements.txt index f6034fa..d9257e0 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,57 +1,58 @@ aiosqlite==0.21.0 -alembic==1.16.1 +alembic==1.17.1 +annotated-doc==0.0.3 annotated-types==0.7.0 -anyio==4.9.0 +anyio==4.11.0 asyncmy==0.2.10 asyncpg==0.30.0 -bcrypt==4.3.0 -certifi==2025.4.26 -cffi==1.17.1 -click==8.2.1 -colorama==0.4.6 -cryptography==45.0.3 -dnspython==2.7.0 +bcrypt==5.0.0 +certifi==2025.10.5 +click==8.3.0 +dnspython==2.8.0 ecdsa==0.19.1 -email_validator==2.2.0 -fastapi==0.115.12 -fastapi-cli==0.0.7 -greenlet==3.2.2 +email-validator==2.3.0 +fastapi==0.121.0 +fastapi-cli==0.0.14 +fastapi-cloud-cli==0.3.1 +greenlet==3.2.4 h11==0.16.0 httpcore==1.0.9 -httptools==0.6.4 +httptools==0.7.1 httpx==0.28.1 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 Jinja2==3.1.6 Mako==1.3.10 -markdown-it-py==3.0.0 -MarkupSafe==3.0.2 +markdown-it-py==4.0.0 +MarkupSafe==3.0.3 mdurl==0.1.2 packaging==25.0 pluggy==1.6.0 pyasn1==0.6.1 -pycparser==2.22 -pydantic==2.11.5 -pydantic_core==2.33.2 -Pygments==2.19.1 -pytest==8.4.1 -pytest-asyncio==1.0.0 -python-dotenv==1.1.0 +pydantic==2.12.4 +pydantic_core==2.41.5 +Pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==1.2.0 +python-dotenv==1.2.1 python-jose==3.5.0 python-multipart==0.0.20 -PyYAML==6.0.2 -rich==14.0.0 -rich-toolkit==0.14.7 +PyYAML==6.0.3 +rich==14.2.0 +rich-toolkit==0.15.1 +rignore==0.7.6 rsa==4.9.1 +sentry-sdk==2.43.0 shellingham==1.5.4 six==1.17.0 sniffio==1.3.1 -SQLAlchemy==2.0.41 -starlette==0.46.2 -typer==0.16.0 -typing-inspection==0.4.1 -typing_extensions==4.14.0 -uvicorn==0.34.3 +SQLAlchemy==2.0.44 +starlette==0.49.3 +typer==0.20.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +urllib3==2.5.0 +uvicorn==0.38.0 uvloop==0.22.1 -watchfiles==1.0.5 +watchfiles==1.1.1 websockets==15.0.1 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 02430c8..564480b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,13 +13,13 @@ "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", - "@angular/forms": "^19.2.14", - "@angular/material": "^19.2.18", + "@angular/forms": "^19.2.15", + "@angular/material": "^19.2.19", "@angular/platform-browser": "^19.0.0", - "@angular/platform-browser-dynamic": "^19.2.14", - "@angular/router": "^19.2.14", - "@angular/service-worker": "^19.2.14", - "@bwip-js/browser": "^4.6.0", + "@angular/platform-browser-dynamic": "^19.2.15", + "@angular/router": "^19.2.15", + "@angular/service-worker": "^19.2.15", + "@bwip-js/browser": "^4.7.0", "@ericblade/quagga2": "^1.8.4", "@ngrx/effects": "^19.2.1", "@ngrx/store": "^19.2.1", @@ -34,9 +34,9 @@ "zone.js": "^0.15.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^19.2.14", - "@angular/cli": "^19.2.14", - "@angular/compiler-cli": "^19.2.14", + "@angular-devkit/build-angular": "^19.2.19", + "@angular/cli": "^19.2.19", + "@angular/compiler-cli": "^19.2.15", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.4.0", "karma": "~6.4.0", @@ -44,7 +44,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "typescript": "~5.6.2" } }, @@ -63,13 +63,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1902.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.14.tgz", - "integrity": "sha512-rgMkqOrxedzqLZ8w59T/0YrpWt7LDmGwt+ZhNHE7cn27jZ876yGC2Bhcn58YZh2+R03WEJ9q0ePblaBYz03SMw==", + "version": "0.1902.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.19.tgz", + "integrity": "sha512-iexYDIYpGAeAU7T60bGcfrGwtq1bxpZixYxWuHYiaD1b5baQgNSfd1isGEOh37GgDNsf4In9i2LOLPm0wBdtgQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.14", + "@angular-devkit/core": "19.2.19", "rxjs": "7.8.1" }, "engines": { @@ -89,17 +89,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.14.tgz", - "integrity": "sha512-0K8vZxXdkME31fd6/+WACug8j4eLlU7mxR2/XJvS+VQ+a7bqdEsVddZDkwdWE+Y3ccZXvD/aNLZSEuSKmVFsnA==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.19.tgz", + "integrity": "sha512-uIxi6Vzss6+ycljVhkyPUPWa20w8qxJL9lEn0h6+sX/fhM8Djt0FHIuTQjoX58EoMaQ/1jrXaRaGimkbaFcG9A==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1902.14", - "@angular-devkit/build-webpack": "0.1902.14", - "@angular-devkit/core": "19.2.14", - "@angular/build": "19.2.14", + "@angular-devkit/architect": "0.1902.19", + "@angular-devkit/build-webpack": "0.1902.19", + "@angular-devkit/core": "19.2.19", + "@angular/build": "19.2.19", "@babel/core": "7.26.10", "@babel/generator": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", @@ -110,7 +110,7 @@ "@babel/preset-env": "7.26.9", "@babel/runtime": "7.26.10", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "19.2.14", + "@ngtools/webpack": "19.2.19", "@vitejs/plugin-basic-ssl": "1.2.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -147,7 +147,7 @@ "tslib": "2.8.1", "webpack": "5.98.0", "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.2.0", + "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, @@ -164,7 +164,7 @@ "@angular/localize": "^19.0.0 || ^19.2.0-next.0", "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", - "@angular/ssr": "^19.2.14", + "@angular/ssr": "^19.2.19", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", @@ -225,13 +225,13 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1902.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.14.tgz", - "integrity": "sha512-XDNB8Nlau/v59Ukd6UgBRBRnTnUmC244832SECmMxXHs1ljJMWGlI1img2xPErGd8426rUA9Iws4RkQiqbsybQ==", + "version": "0.1902.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.19.tgz", + "integrity": "sha512-x2tlGg5CsUveFzuRuqeHknSbGirSAoRynEh+KqPRGK0G3WpMViW/M8SuVurecasegfIrDWtYZ4FnVxKqNbKwXQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1902.14", + "@angular-devkit/architect": "0.1902.19", "rxjs": "7.8.1" }, "engines": { @@ -255,9 +255,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.14.tgz", - "integrity": "sha512-aaPEnRNIBoYT4XrrYcZlHadX8vFDTUR+4wUgcmr0cNDLeWzWtoPFeVq8TQD6kFDeqovSx/UVEblGgg/28WvHyg==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", + "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -293,13 +293,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.14.tgz", - "integrity": "sha512-s89/MWXHy8+GP/cRfFbSECIG3FQQQwNVv44OOmghPVgKQgQ+EoE/zygL2hqKYTUPoPaS/IhNXdXjSE5pS9yLeg==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.19.tgz", + "integrity": "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.14", + "@angular-devkit/core": "19.2.19", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", @@ -322,9 +322,9 @@ } }, "node_modules/@angular/animations": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.14.tgz", - "integrity": "sha512-xhl8fLto5HHJdVj8Nb6EoBEiTAcXuWDYn1q5uHcGxyVH3kiwENWy/2OQXgCr2CuWo2e6hNUGzSLf/cjbsMNqEA==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.15.tgz", + "integrity": "sha512-eq9vokLU8bjs7g/Znz8zJUQEOhT0MAJ/heBCHbB35S+CtZXJmItrsEqkI1tsRiR58NKXB6cbhBhULVo6qJbhXQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -333,19 +333,19 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.14", - "@angular/core": "19.2.14" + "@angular/common": "19.2.15", + "@angular/core": "19.2.15" } }, "node_modules/@angular/build": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.14.tgz", - "integrity": "sha512-PAUR8vZpGKXy0Vc5gpJkigOthoj5YeGDpeykl/yLi6sx6yAIlXcE0MD+LGehKeqFSBL56rEpn9n710lI7eTJwg==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.19.tgz", + "integrity": "sha512-SFzQ1bRkNFiOVu+aaz+9INmts7tDUrsHLEr9HmARXr9qk5UmR8prlw39p2u+Bvi6/lCiJ18TZMQQl9mGyr63lg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1902.14", + "@angular-devkit/architect": "0.1902.19", "@babel/core": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", @@ -368,7 +368,7 @@ "sass": "1.85.0", "semver": "7.7.1", "source-map-support": "0.5.21", - "vite": "6.2.7", + "vite": "6.4.1", "watchpack": "2.4.2" }, "engines": { @@ -385,7 +385,7 @@ "@angular/localize": "^19.0.0 || ^19.2.0-next.0", "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", - "@angular/ssr": "^19.2.14", + "@angular/ssr": "^19.2.19", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^19.0.0 || ^19.2.0-next.0", @@ -423,111 +423,10 @@ } } }, - "node_modules/@angular/build/node_modules/vite": { - "version": "6.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.7.tgz", - "integrity": "sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "postcss": "^8.5.3", - "rollup": "^4.30.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/vite/node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/@angular/cdk": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.18.tgz", - "integrity": "sha512-aGMHOYK/VV9PhxGTUDwiu/4ozoR/RKz8cimI+QjRxEBhzn4EPqjUDSganvlhmgS7cTN3+aqozdvF/GopMRJjLg==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.19.tgz", + "integrity": "sha512-PCpJagurPBqciqcq4Z8+3OtKLb7rSl4w/qBJoIMua8CgnrjvA1i+SWawhdtfI1zlY8FSwhzLwXV0CmWWfFzQPg==", "license": "MIT", "dependencies": { "parse5": "^7.1.2", @@ -540,18 +439,18 @@ } }, "node_modules/@angular/cli": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.14.tgz", - "integrity": "sha512-jZvNHAwmyhgUqSIs6OW8YH1rX9XKytm4zPxJol1Xk56F8yAhnrUtukcOi3b7Dv19Z+9eXkwV/Db+2dGjWIE0DA==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.19.tgz", + "integrity": "sha512-e9tAzFNOL4mMWfMnpC9Up83OCTOp2siIj8W41FCp8jfoEnw79AXDDLh3d70kOayiObchksTJVShslTogLUyhMw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1902.14", - "@angular-devkit/core": "19.2.14", - "@angular-devkit/schematics": "19.2.14", + "@angular-devkit/architect": "0.1902.19", + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", "@inquirer/prompts": "7.3.2", "@listr2/prompt-adapter-inquirer": "2.0.18", - "@schematics/angular": "19.2.14", + "@schematics/angular": "19.2.19", "@yarnpkg/lockfile": "1.1.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", @@ -574,9 +473,9 @@ } }, "node_modules/@angular/common": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.14.tgz", - "integrity": "sha512-NcNklcuyqaTjOVGf7aru8APX9mjsnZ01gFZrn47BxHozhaR0EMRrotYQTdi8YdVjPkeYFYanVntSLfhyobq/jg==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.15.tgz", + "integrity": "sha512-aVa/ctBYH/4qgA7r4sS7TV+/DzRYmcS+3d6l89pNKUXkI8gpmsd+r3FjccaemX4Wqru1QOrMvC+i+e7IBIVv0g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -585,14 +484,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "19.2.14", + "@angular/core": "19.2.15", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.14.tgz", - "integrity": "sha512-ZqJDYOdhgKpVGNq3+n/Gbxma8DVYElDsoRe0tvNtjkWBVdaOxdZZUqmJ3kdCBsqD/aqTRvRBu0KGo9s2fCChkA==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.15.tgz", + "integrity": "sha512-hMHZU6/03xG0tbPDIm1hbVSTFLnRkGYfh+xdBwUMnIFYYTS0QJ2hdPfEZKCJIXm+fz9IAI5MPdDTfeyp0sgaHQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -602,9 +501,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.14.tgz", - "integrity": "sha512-e9/h86ETjoIK2yTLE9aUeMCKujdg/du2pq7run/aINjop4RtnNOw+ZlSTUa6R65lP5CVwDup1kPytpAoifw8cA==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.15.tgz", + "integrity": "sha512-4r5tvGA2Ok3o8wROZBkF9qNKS7L0AEpdBIkAVJbLw2rBY2SlyycFIRYyV2+D1lJ1jq/f9U7uN6oon0MjTvNYkA==", "dev": true, "license": "MIT", "dependencies": { @@ -626,7 +525,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "19.2.14", + "@angular/compiler": "19.2.15", "typescript": ">=5.5 <5.9" } }, @@ -679,9 +578,9 @@ } }, "node_modules/@angular/core": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.14.tgz", - "integrity": "sha512-EVErpW9tGqJ/wNcAN3G/ErH8pHCJ8mM1E6bsJ8UJIpDTZkpqqYjBMtZS9YWH5n3KwUd1tAkAB2w8FK125AjDUQ==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.15.tgz", + "integrity": "sha512-PxhzCwwm23N4Mq6oV7UPoYiJF4r6FzGhRSxOBBlEp322k7zEQbIxd/XO6F3eoG73qC1UsOXMYYv6GnQpx42y3A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -695,9 +594,9 @@ } }, "node_modules/@angular/forms": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.14.tgz", - "integrity": "sha512-hWtDOj2B0AuRTf+nkMJeodnFpDpmEK9OIhIv1YxcRe73ooaxrIdjgugkElO8I9Tj0E4/7m117ezhWDUkbqm1zA==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.15.tgz", + "integrity": "sha512-pZDElcYPmNzPxvWJpZQCIizsNApDIfk9xLJE4I8hzLISfWGbQvfjuuarDAuQZEXudeLXoDOstDXkDja40muLGg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -706,22 +605,22 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.14", - "@angular/core": "19.2.14", - "@angular/platform-browser": "19.2.14", + "@angular/common": "19.2.15", + "@angular/core": "19.2.15", + "@angular/platform-browser": "19.2.15", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.18.tgz", - "integrity": "sha512-xxedRQ9u7aiUYVrHAxASLUxnofN29xaqEGhBcHLAfOsFXdDMwDe/2ly79iKufwEs5BFBm3nfhJoarXZ3+8pucQ==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.19.tgz", + "integrity": "sha512-auIE6JUzTIA3LyYklh9J/T7u64crmphxUBgAa0zcOMDog6SYfwbNe9YeLQqua5ek4OUAOdK/BHHfVl5W5iaUoQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/cdk": "19.2.18", + "@angular/cdk": "19.2.19", "@angular/common": "^19.0.0 || ^20.0.0", "@angular/core": "^19.0.0 || ^20.0.0", "@angular/forms": "^19.0.0 || ^20.0.0", @@ -730,9 +629,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.14.tgz", - "integrity": "sha512-hzkT5nmA64oVBQl6PRjdL4dIFT1n7lfM9rm5cAoS+6LUUKRgiE2d421Kpn/Hz3jaCJfo+calMIdtSMIfUJBmww==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.15.tgz", + "integrity": "sha512-OelQ6weCjon8kZD8kcqNzwugvZJurjS3uMJCwsA2vXmP/3zJ31SWtNqE2zLT1R2csVuwnp0h+nRMgq+pINU7Rg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -741,9 +640,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "19.2.14", - "@angular/common": "19.2.14", - "@angular/core": "19.2.14" + "@angular/animations": "19.2.15", + "@angular/common": "19.2.15", + "@angular/core": "19.2.15" }, "peerDependenciesMeta": { "@angular/animations": { @@ -752,9 +651,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.14.tgz", - "integrity": "sha512-Hfz0z1KDQmIdnFXVFCwCPykuIsHPkr1uW2aY396eARwZ6PK8i0Aadcm1ZOnpd3MR1bMyDrJo30VRS5kx89QWvA==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.15.tgz", + "integrity": "sha512-dKy0SS395FCh8cW9AQ8nf4Wn3XlONaH7z50T1bGxm3eOoRqjxJYyIeIlEbDdJakMz4QPR3dGr81HleZd8TJumQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -763,16 +662,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.14", - "@angular/compiler": "19.2.14", - "@angular/core": "19.2.14", - "@angular/platform-browser": "19.2.14" + "@angular/common": "19.2.15", + "@angular/compiler": "19.2.15", + "@angular/core": "19.2.15", + "@angular/platform-browser": "19.2.15" } }, "node_modules/@angular/router": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.14.tgz", - "integrity": "sha512-cBTWY9Jx7YhbmDYDb7Hqz4Q7UNIMlKTkdKToJd2pbhIXyoS+kHVQrySmyca+jgvYMjWnIjsAEa3dpje12D4mFw==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.15.tgz", + "integrity": "sha512-0TM1D8S7RQ00drKy7hA/ZLBY14dUBqFBgm06djcNcOjNzVAtgkeV0i+0Smq9tCC7UsGKdpZu4RgfYjHATBNlTQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -781,16 +680,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.14", - "@angular/core": "19.2.14", - "@angular/platform-browser": "19.2.14", + "@angular/common": "19.2.15", + "@angular/core": "19.2.15", + "@angular/platform-browser": "19.2.15", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/service-worker": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-19.2.14.tgz", - "integrity": "sha512-ajH4kjsuzDvJNxnG18y8N47R0avXFKwOeLszoiirlr5160C+k4HmQvIbzcCjD5liW0OkmxJN1cMW6KdilP8/2w==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-19.2.15.tgz", + "integrity": "sha512-H40T8ni4Inx3tNinoQvcEmDsgFErjfzEWnJpoAonDQkzarlu1Qr8vBgv+DX+KzuMKZ3xtsWW9vFnAI4/DilnDw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -802,7 +701,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "19.2.14", + "@angular/core": "19.2.15", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -822,9 +721,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -937,18 +836,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -982,14 +881,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -1023,31 +922,41 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1068,15 +977,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1195,9 +1104,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -1215,42 +1124,42 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -1260,14 +1169,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1327,14 +1236,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1474,9 +1383,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", "dev": true, "license": "MIT", "dependencies": { @@ -1507,13 +1416,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -1524,18 +1433,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1575,13 +1484,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1657,9 +1567,9 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", "dev": true, "license": "MIT", "dependencies": { @@ -1756,9 +1666,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", "dev": true, "license": "MIT", "dependencies": { @@ -1822,16 +1732,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1923,16 +1833,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1975,9 +1886,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1992,9 +1903,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "license": "MIT", "dependencies": { @@ -2072,9 +1983,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", "dev": true, "license": "MIT", "dependencies": { @@ -2454,35 +2365,35 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -2490,23 +2401,23 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@bwip-js/browser": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@bwip-js/browser/-/browser-4.6.0.tgz", - "integrity": "sha512-HefyK7t5SIM3t0Hy9RiE22iNLf/2Fd7hFa+2PcwDUVQOwEkBcXrzkCoMNhdcfzEe1CD61xhSjh7GYdbnC4bPQA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@bwip-js/browser/-/browser-4.7.0.tgz", + "integrity": "sha512-gwPl8kaTRnxgr3DdSRqlCNTfOjWAZexh3ouazBin5iJagY4XCmYD8y5RBsLOVkoQ1ahWvTKdLlfmVwOz6N20qQ==", "license": "MIT" }, "node_modules/@colors/colors": { @@ -2974,18 +2885,28 @@ "node": ">=18" } }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@inquirer/checkbox": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", - "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.1.tgz", + "integrity": "sha512-rOcLotrptYIy59SGQhKlU0xBg1vvcVl2FdPIEclUvKHh0wo12OfGkId/01PIMJ/V+EimJ77t085YabgnQHBa5A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.1", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3022,20 +2943,20 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", - "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.1.tgz", + "integrity": "sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", + "mute-stream": "^3.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3050,15 +2971,15 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", - "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.22.tgz", + "integrity": "sha512-8yYZ9TCbBKoBkzHtVNMF6PV1RJEUvMlhvmS3GxH4UvXMEHlS45jFyqFy0DU+K42jBs5slOaA78xGqqqWAx3u6A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "external-editor": "^3.1.0" + "@inquirer/core": "^10.3.1", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3073,15 +2994,37 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", - "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.22.tgz", + "integrity": "sha512-9XOjCjvioLjwlq4S4yXzhvBmAXj5tG+jvva0uqedEsQ9VD8kZ+YT7ap23i0bIXOtow+di4+u3i6u26nDqEfY4Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.1", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" }, "engines": { "node": ">=18" @@ -3096,9 +3039,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", "engines": { @@ -3106,14 +3049,14 @@ } }, "node_modules/@inquirer/input": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", - "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.0.tgz", + "integrity": "sha512-h4fgse5zeGsBSW3cRQqu9a99OXRdRsNCvHoBqVmz40cjYjYFzcfwD0KA96BHIPlT7rZw0IpiefQIqXrjbzjS4Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.3.1", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3128,14 +3071,14 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", - "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.22.tgz", + "integrity": "sha512-oAdMJXz++fX58HsIEYmvuf5EdE8CfBHHXjoi9cTcQzgFoHGZE+8+Y3P38MlaRMeBvAVnkWtAxMUF6urL2zYsbg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.3.1", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3150,15 +3093,15 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", - "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "version": "4.0.22", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.22.tgz", + "integrity": "sha512-CbdqK1ioIr0Y3akx03k/+Twf+KSlHjn05hBL+rmubMll7PsDTGH0R4vfFkr+XrkB0FOHrjIwVP9crt49dgt+1g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.1", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3203,15 +3146,15 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", - "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.10.tgz", + "integrity": "sha512-Du4uidsgTMkoH5izgpfyauTL/ItVHOLsVdcY+wGeoGaG56BV+/JfmyoQGniyhegrDzXpfn3D+LFHaxMDRygcAw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.1", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3226,16 +3169,16 @@ } }, "node_modules/@inquirer/search": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", - "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.1.tgz", + "integrity": "sha512-cKiuUvETublmTmaOneEermfG2tI9ABpb7fW/LqzZAnSv4ZaJnbEis05lOkiBuYX5hNdnX0Q9ryOQyrNidb55WA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.1", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3250,17 +3193,17 @@ } }, "node_modules/@inquirer/select": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", - "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.1.tgz", + "integrity": "sha512-E9hbLU4XsNe2SAOSsFrtYtYQDVi1mfbqJrPDvXKnGlnRiApBdWMJz7r3J2Ff38AqULkPUD3XjQMD4492TymD7Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.1", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3275,9 +3218,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", "engines": { @@ -3311,9 +3254,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -3390,18 +3333,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3414,20 +3353,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { @@ -3436,16 +3365,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3470,17 +3399,76 @@ "tslib": "2" } }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", - "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/base64": "^1.1.1", - "@jsonjoy.com/util": "^1.1.2", + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", - "thingies": "^1.20.0" + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" }, "engines": { "node": ">=10.0" @@ -3494,11 +3482,15 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", - "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, "engines": { "node": ">=10.0" }, @@ -3725,9 +3717,9 @@ ] }, "node_modules/@napi-rs/nice": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", - "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, "license": "MIT", "optional": true, @@ -3739,28 +3731,29 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.0.1", - "@napi-rs/nice-android-arm64": "1.0.1", - "@napi-rs/nice-darwin-arm64": "1.0.1", - "@napi-rs/nice-darwin-x64": "1.0.1", - "@napi-rs/nice-freebsd-x64": "1.0.1", - "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", - "@napi-rs/nice-linux-arm64-gnu": "1.0.1", - "@napi-rs/nice-linux-arm64-musl": "1.0.1", - "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", - "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", - "@napi-rs/nice-linux-s390x-gnu": "1.0.1", - "@napi-rs/nice-linux-x64-gnu": "1.0.1", - "@napi-rs/nice-linux-x64-musl": "1.0.1", - "@napi-rs/nice-win32-arm64-msvc": "1.0.1", - "@napi-rs/nice-win32-ia32-msvc": "1.0.1", - "@napi-rs/nice-win32-x64-msvc": "1.0.1" + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", - "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", "cpu": [ "arm" ], @@ -3775,9 +3768,9 @@ } }, "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", - "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", "cpu": [ "arm64" ], @@ -3792,9 +3785,9 @@ } }, "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", "cpu": [ "arm64" ], @@ -3809,9 +3802,9 @@ } }, "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", - "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", "cpu": [ "x64" ], @@ -3826,9 +3819,9 @@ } }, "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", "cpu": [ "x64" ], @@ -3843,9 +3836,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", "cpu": [ "arm" ], @@ -3860,9 +3853,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", "cpu": [ "arm64" ], @@ -3877,9 +3870,9 @@ } }, "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", "cpu": [ "arm64" ], @@ -3894,9 +3887,9 @@ } }, "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", "cpu": [ "ppc64" ], @@ -3911,9 +3904,9 @@ } }, "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", - "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", "cpu": [ "riscv64" ], @@ -3928,9 +3921,9 @@ } }, "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", "cpu": [ "s390x" ], @@ -3945,9 +3938,9 @@ } }, "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", "cpu": [ "x64" ], @@ -3962,9 +3955,9 @@ } }, "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", "cpu": [ "x64" ], @@ -3978,10 +3971,27 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", "cpu": [ "arm64" ], @@ -3996,9 +4006,9 @@ } }, "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", - "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", "cpu": [ "ia32" ], @@ -4013,9 +4023,9 @@ } }, "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", "cpu": [ "x64" ], @@ -4071,9 +4081,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.14.tgz", - "integrity": "sha512-PqrY+eeSUoF6JC6NCEQRPE/0Y2umSllD/fsDE6pnQrvGfztBpj0Jt1WMhgEI8BBcl4S7QW0LhPynkBmnCvTUmw==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.19.tgz", + "integrity": "sha512-R9aeTrOBiRVl8I698JWPniUAAEpSvzc8SUGWSM5UXWMcHnWqd92cOnJJ1aXDGJZKXrbhMhCBx9Dglmcks5IDpg==", "dev": true, "license": "MIT", "engines": { @@ -4335,9 +4345,9 @@ } }, "node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", - "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", "dev": true, "license": "ISC", "dependencies": { @@ -4910,6 +4920,20 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz", + "integrity": "sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", @@ -4938,6 +4962,20 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz", + "integrity": "sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", @@ -4953,9 +4991,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", - "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz", + "integrity": "sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==", "cpu": [ "riscv64" ], @@ -4964,8 +5002,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.34.8", @@ -5009,6 +5046,20 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz", + "integrity": "sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", @@ -5037,6 +5088,20 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz", + "integrity": "sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", @@ -5052,14 +5117,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.14.tgz", - "integrity": "sha512-p/jvMwth67g7tOrziTx+yWRagIPtjx21TF2uU2Pv5bqTY+JjRTczJs3yHPmVpzJN+ptmw47K4/NeLJmVUGuBgA==", + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.19.tgz", + "integrity": "sha512-6/0pvbPCY4UHeB4lnM/5r250QX5gcLgOYbR5FdhFu+22mOPHfWpRc5tNuY9kCephDHzAHjo6fTW1vefOOmA4jw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.14", - "@angular-devkit/schematics": "19.2.14", + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", "jsonc-parser": "3.3.1" }, "engines": { @@ -5300,35 +5365,22 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "dev": true, "license": "MIT", "dependencies": { @@ -5346,9 +5398,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -5356,9 +5408,9 @@ } }, "node_modules/@types/jasmine": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.8.tgz", - "integrity": "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==", + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.12.tgz", + "integrity": "sha512-1BzPxNsFDLDfj9InVR3IeY0ZVf4o9XV+4mDqoCfyPkbsA7dYyKAPAb2co6wLFlHcvxPlt1wShm7zQdV7uTfLGA==", "dev": true, "license": "MIT" }, @@ -5377,19 +5429,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", - "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "dev": true, "license": "MIT", "dependencies": { @@ -5418,13 +5470,12 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -5439,15 +5490,26 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -5776,9 +5838,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -5844,16 +5906,16 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5873,9 +5935,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -6038,14 +6100,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -6077,13 +6139,13 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6127,6 +6189,16 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6233,6 +6305,19 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6283,9 +6368,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "dev": true, "funding": [ { @@ -6303,10 +6388,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -6461,34 +6547,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", + "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "engines": { @@ -6547,9 +6616,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001722", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", - "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", "dev": true, "funding": [ { @@ -6591,9 +6660,9 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, @@ -6882,9 +6951,9 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { @@ -6892,7 +6961,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -7071,13 +7140,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", - "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", + "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.0" + "browserslist": "^4.26.3" }, "funding": { "type": "opencollective", @@ -7085,10 +7154,9 @@ } }, "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "license": "MIT" }, "node_modules/cors": { @@ -7200,9 +7268,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7217,9 +7285,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -7287,9 +7355,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -7391,9 +7459,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7532,12 +7600,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "license": "MIT" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7546,16 +7608,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.166", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", - "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "version": "1.5.249", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.249.tgz", + "integrity": "sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -7654,9 +7716,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "license": "MIT", "dependencies": { @@ -7741,9 +7803,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7946,9 +8008,9 @@ } }, "node_modules/exponential-backoff": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", - "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, @@ -8071,21 +8133,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -8138,9 +8185,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -8178,11 +8225,14 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -8306,9 +8356,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -8480,9 +8530,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, "license": "MIT", "engines": { @@ -8560,9 +8610,9 @@ } }, "node_modules/gl-matrix": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", - "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", "license": "MIT" }, "node_modules/glob": { @@ -8600,6 +8650,23 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -8607,16 +8674,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/globby": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", @@ -9003,16 +9060,20 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/icss-utils": { @@ -9113,9 +9174,9 @@ } }, "node_modules/immutable": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", - "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "dev": true, "license": "MIT" }, @@ -9182,15 +9243,11 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -9329,9 +9386,9 @@ } }, "node_modules/is-network-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", - "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", "dev": true, "license": "MIT", "engines": { @@ -9546,9 +9603,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9650,10 +9707,9 @@ } }, "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "license": "MIT" }, "node_modules/jsesc": { @@ -10026,21 +10082,11 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, - "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, "node_modules/karma/node_modules/wrap-ansi": { @@ -10101,14 +10147,14 @@ } }, "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/less": { @@ -10260,9 +10306,9 @@ } }, "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -10280,9 +10326,9 @@ "license": "MIT" }, "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -10325,13 +10371,17 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -10410,26 +10460,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -10440,13 +10474,13 @@ } }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.0.0" + "get-east-asian-width": "^1.3.1" }, "engines": { "node": ">=18" @@ -10456,9 +10490,9 @@ } }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { @@ -10473,9 +10507,9 @@ } }, "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -10599,20 +10633,19 @@ } }, "node_modules/memfs": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", - "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.50.0.tgz", + "integrity": "sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/json-pack": "^1.0.3", - "@jsonjoy.com/util": "^1.3.0", - "tree-dump": "^1.0.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", "tslib": "^2.0.0" }, - "engines": { - "node": ">= 4.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" @@ -10931,9 +10964,9 @@ "license": "ISC" }, "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10974,9 +11007,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", - "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", "dev": true, "license": "MIT", "optional": true, @@ -11022,13 +11055,13 @@ } }, "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nanoid": { @@ -11152,9 +11185,9 @@ } }, "node_modules/node-gyp": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", - "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11212,34 +11245,17 @@ "node": ">=16" } }, - "node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", + "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "engines": { @@ -11273,9 +11289,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -11329,9 +11345,9 @@ } }, "node_modules/npm-install-checks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", - "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -11488,9 +11504,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -11640,23 +11656,13 @@ } }, "node_modules/ordered-binary": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", - "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", + "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", "dev": true, "license": "MIT", "optional": true }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -12195,9 +12201,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -12384,6 +12390,19 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -12428,9 +12447,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -12455,18 +12474,18 @@ "license": "MIT" }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -12480,31 +12499,18 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -12546,16 +12552,6 @@ "node": ">=0.6" } }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12758,9 +12754,9 @@ } }, "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "engines": { @@ -12910,17 +12906,17 @@ } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "optional": true }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -13360,9 +13356,9 @@ } }, "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -13493,14 +13489,24 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -13628,9 +13634,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, @@ -13666,13 +13672,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -13698,12 +13697,6 @@ "node": ">=0.10.0" } }, - "node_modules/sshpk/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "license": "MIT" - }, "node_modules/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", @@ -13827,9 +13820,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -13903,13 +13896,17 @@ } }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -14068,14 +14065,18 @@ } }, "node_modules/thingies": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", - "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", "dev": true, - "license": "Unlicense", + "license": "MIT", "engines": { "node": ">=10.18" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, "peerDependencies": { "tslib": "^2" } @@ -14094,14 +14095,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -14110,17 +14111,27 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/to-regex-range": { @@ -14169,9 +14180,9 @@ } }, "node_modules/tree-dump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", - "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -14211,15 +14222,15 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz", - "integrity": "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", + "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", "dev": true, "license": "MIT", "dependencies": { "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -14243,19 +14254,6 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -14292,9 +14290,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.40", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", - "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", "dev": true, "funding": [ { @@ -14319,9 +14317,9 @@ } }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -14350,9 +14348,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", "engines": { @@ -14360,9 +14358,9 @@ } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "license": "MIT", "engines": { @@ -14435,9 +14433,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -14501,13 +14499,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "bin/uuid" } }, "node_modules/validate-npm-package-license": { @@ -14522,9 +14520,9 @@ } }, "node_modules/validate-npm-package-name": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.1.tgz", - "integrity": "sha512-OaI//3H0J7ZkR1OqlhGA8cA+Cbk/2xFOQpJOt5+s27/ta9eZwpeervh4Mxh4w0im/kdgktowaqVNR7QOrUd7Yg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "dev": true, "license": "ISC", "engines": { @@ -14555,19 +14553,12 @@ "extsprintf": "^1.2.0" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "license": "MIT" - }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -14638,9 +14629,9 @@ } }, "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", - "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz", + "integrity": "sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==", "cpu": [ "arm" ], @@ -14649,13 +14640,12 @@ "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-android-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", - "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz", + "integrity": "sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==", "cpu": [ "arm64" ], @@ -14664,13 +14654,12 @@ "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", - "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz", + "integrity": "sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==", "cpu": [ "arm64" ], @@ -14679,13 +14668,12 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", - "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz", + "integrity": "sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==", "cpu": [ "x64" ], @@ -14694,13 +14682,12 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", - "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz", + "integrity": "sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==", "cpu": [ "arm64" ], @@ -14709,13 +14696,12 @@ "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", - "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz", + "integrity": "sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==", "cpu": [ "x64" ], @@ -14724,13 +14710,12 @@ "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", - "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz", + "integrity": "sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==", "cpu": [ "arm" ], @@ -14739,13 +14724,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", - "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz", + "integrity": "sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==", "cpu": [ "arm" ], @@ -14754,13 +14738,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", - "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz", + "integrity": "sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==", "cpu": [ "arm64" ], @@ -14769,13 +14752,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", - "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz", + "integrity": "sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==", "cpu": [ "arm64" ], @@ -14784,43 +14766,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", - "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", - "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", - "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz", + "integrity": "sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==", "cpu": [ "riscv64" ], @@ -14829,13 +14780,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", - "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz", + "integrity": "sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==", "cpu": [ "s390x" ], @@ -14844,13 +14794,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", - "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz", + "integrity": "sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==", "cpu": [ "x64" ], @@ -14859,13 +14808,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", - "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz", + "integrity": "sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==", "cpu": [ "x64" ], @@ -14874,13 +14822,12 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", - "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz", + "integrity": "sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==", "cpu": [ "arm64" ], @@ -14889,13 +14836,12 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", - "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz", + "integrity": "sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==", "cpu": [ "ia32" ], @@ -14904,13 +14850,12 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", - "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz", + "integrity": "sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==", "cpu": [ "x64" ], @@ -14919,21 +14864,19 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/vite/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vite/node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -14950,7 +14893,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14961,14 +14903,13 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", - "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", + "version": "4.53.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.1.tgz", + "integrity": "sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -14978,26 +14919,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.43.0", - "@rollup/rollup-android-arm64": "4.43.0", - "@rollup/rollup-darwin-arm64": "4.43.0", - "@rollup/rollup-darwin-x64": "4.43.0", - "@rollup/rollup-freebsd-arm64": "4.43.0", - "@rollup/rollup-freebsd-x64": "4.43.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", - "@rollup/rollup-linux-arm-musleabihf": "4.43.0", - "@rollup/rollup-linux-arm64-gnu": "4.43.0", - "@rollup/rollup-linux-arm64-musl": "4.43.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", - "@rollup/rollup-linux-riscv64-gnu": "4.43.0", - "@rollup/rollup-linux-riscv64-musl": "4.43.0", - "@rollup/rollup-linux-s390x-gnu": "4.43.0", - "@rollup/rollup-linux-x64-gnu": "4.43.0", - "@rollup/rollup-linux-x64-musl": "4.43.0", - "@rollup/rollup-win32-arm64-msvc": "4.43.0", - "@rollup/rollup-win32-ia32-msvc": "4.43.0", - "@rollup/rollup-win32-x64-msvc": "4.43.0", + "@rollup/rollup-android-arm-eabi": "4.53.1", + "@rollup/rollup-android-arm64": "4.53.1", + "@rollup/rollup-darwin-arm64": "4.53.1", + "@rollup/rollup-darwin-x64": "4.53.1", + "@rollup/rollup-freebsd-arm64": "4.53.1", + "@rollup/rollup-freebsd-x64": "4.53.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.1", + "@rollup/rollup-linux-arm-musleabihf": "4.53.1", + "@rollup/rollup-linux-arm64-gnu": "4.53.1", + "@rollup/rollup-linux-arm64-musl": "4.53.1", + "@rollup/rollup-linux-loong64-gnu": "4.53.1", + "@rollup/rollup-linux-ppc64-gnu": "4.53.1", + "@rollup/rollup-linux-riscv64-gnu": "4.53.1", + "@rollup/rollup-linux-riscv64-musl": "4.53.1", + "@rollup/rollup-linux-s390x-gnu": "4.53.1", + "@rollup/rollup-linux-x64-gnu": "4.53.1", + "@rollup/rollup-linux-x64-musl": "4.53.1", + "@rollup/rollup-openharmony-arm64": "4.53.1", + "@rollup/rollup-win32-arm64-msvc": "4.53.1", + "@rollup/rollup-win32-ia32-msvc": "4.53.1", + "@rollup/rollup-win32-x64-gnu": "4.53.1", + "@rollup/rollup-win32-x64-msvc": "4.53.1", "fsevents": "~2.3.2" } }, @@ -15131,15 +15074,16 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", - "integrity": "sha512-90SqqYXA2SK36KcT6o1bvwvZfJFcmoamqeJY7+boioffX9g9C0wjjJRGUrQIuh43pb0ttX7+ssavmj/WN2RHtA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", "@types/serve-index": "^1.9.4", "@types/serve-static": "^1.15.5", "@types/sockjs": "^0.3.36", @@ -15152,7 +15096,7 @@ "connect-history-api-fallback": "^2.0.0", "express": "^4.21.2", "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.7", + "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", @@ -15277,9 +15221,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -15314,9 +15258,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz", - "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", "engines": { @@ -15685,9 +15629,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "dev": true, "license": "MIT", "engines": { diff --git a/frontend/package.json b/frontend/package.json index d6056fc..78467ca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,13 +18,13 @@ "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", - "@angular/forms": "^19.2.14", - "@angular/material": "^19.2.18", + "@angular/forms": "^19.2.15", + "@angular/material": "^19.2.19", "@angular/platform-browser": "^19.0.0", - "@angular/platform-browser-dynamic": "^19.2.14", - "@angular/router": "^19.2.14", - "@angular/service-worker": "^19.2.14", - "@bwip-js/browser": "^4.6.0", + "@angular/platform-browser-dynamic": "^19.2.15", + "@angular/router": "^19.2.15", + "@angular/service-worker": "^19.2.15", + "@bwip-js/browser": "^4.7.0", "@ericblade/quagga2": "^1.8.4", "@ngrx/effects": "^19.2.1", "@ngrx/store": "^19.2.1", @@ -39,9 +39,9 @@ "zone.js": "^0.15.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^19.2.14", - "@angular/cli": "^19.2.14", - "@angular/compiler-cli": "^19.2.14", + "@angular-devkit/build-angular": "^19.2.19", + "@angular/cli": "^19.2.19", + "@angular/compiler-cli": "^19.2.15", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.4.0", "karma": "~6.4.0", @@ -49,7 +49,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "typescript": "~5.6.2" } -} \ No newline at end of file +}