diff --git a/.gitignore b/.gitignore
index ef6c904..b79746c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -100,6 +100,7 @@ helm/charts/
# CMake и сборка
CMakeCache.txt
+CMakeLists.txt
CMakeFiles/
CMakeScripts/
Makefile
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index cc42e2a..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 Finnik & S1gmagor
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/Readme.md b/Readme.md
index ad9f586..77b1820 100644
--- a/Readme.md
+++ b/Readme.md
@@ -2,21 +2,31 @@
-
-
+[](https://github.com/CS151512/STREMO)
+[](./LICENSE)
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -144,4 +154,7 @@ pdflatex main.tex
```
---
-**by finnik & s1gmagor**
+**by finnik && s1gmagor**
+
+* **Оригинальный дизайн:** [где-то Dribble, я просто уже не могу найти :(](https://dribbble.com/search/shots/popular/mobile?q=streaming-service)
+* **Статус:** Данный проект разработан исключительно в образовательных целях (университетская расчетно-графическая работа) и не имеет коммерческой выгоды. Права на исходный визуальный концепт принадлежат его автору. Не надо бить палками...
diff --git a/backend/service/auth-service/CMakeLists.txt b/backend/service/auth-service/CMakeLists.txt
deleted file mode 100644
index 3e74937..0000000
--- a/backend/service/auth-service/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-cmake_minimum_required(VERSION 3.20)
-project(auth_service)
-
-add_executable(auth_service src/main.cpp)
-target_link_libraries(auth_service PRIVATE cpp_core)
diff --git a/backend/service/auth-service/include/service/logic.hpp b/backend/service/auth-service/include/service/logic.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/service/manager.hpp b/backend/service/auth-service/include/service/manager.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/service/validator.hpp b/backend/service/auth-service/include/service/validator.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/utils/argon2_hasher.hpp b/backend/service/auth-service/include/utils/argon2_hasher.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/utils/config_parser.hpp b/backend/service/auth-service/include/utils/config_parser.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/utils/jwt_signer.hpp b/backend/service/auth-service/include/utils/jwt_signer.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/__init__.py b/backend/service/auth-service/src/__init__.py
new file mode 100644
index 0000000..098642c
--- /dev/null
+++ b/backend/service/auth-service/src/__init__.py
@@ -0,0 +1,2 @@
+"""auth-service - microservice for authentication & authorization"""
+__version__ = "0.1.0"
\ No newline at end of file
diff --git a/backend/service/auth-service/src/api/__init__.py b/backend/service/auth-service/src/api/__init__.py
new file mode 100644
index 0000000..1bdda86
--- /dev/null
+++ b/backend/service/auth-service/src/api/__init__.py
@@ -0,0 +1 @@
+"""API: HTTP & gRPC interfaces"""
\ No newline at end of file
diff --git a/backend/service/auth-service/include/api/grpc/handlers.hpp b/backend/service/auth-service/src/api/dependencies.py
similarity index 100%
rename from backend/service/auth-service/include/api/grpc/handlers.hpp
rename to backend/service/auth-service/src/api/dependencies.py
diff --git a/backend/service/auth-service/src/api/grpc/__init__.py b/backend/service/auth-service/src/api/grpc/__init__.py
new file mode 100644
index 0000000..5424fac
--- /dev/null
+++ b/backend/service/auth-service/src/api/grpc/__init__.py
@@ -0,0 +1 @@
+"""gRPC API"""
\ No newline at end of file
diff --git a/backend/service/auth-service/src/api/grpc/handlers.cpp b/backend/service/auth-service/src/api/grpc/handlers.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/api/grpc/server.hpp b/backend/service/auth-service/src/api/grpc/handlers.py
similarity index 100%
rename from backend/service/auth-service/include/api/grpc/server.hpp
rename to backend/service/auth-service/src/api/grpc/handlers.py
diff --git a/backend/service/auth-service/src/api/grpc/server.cpp b/backend/service/auth-service/src/api/grpc/server.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/api/grpc_handler.hpp b/backend/service/auth-service/src/api/grpc/server.py
similarity index 100%
rename from backend/service/auth-service/include/api/grpc_handler.hpp
rename to backend/service/auth-service/src/api/grpc/server.py
diff --git a/backend/service/auth-service/src/api/grpc_handler.cpp b/backend/service/auth-service/src/api/grpc_handler.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/api/http/__init__.py b/backend/service/auth-service/src/api/http/__init__.py
new file mode 100644
index 0000000..35b3a90
--- /dev/null
+++ b/backend/service/auth-service/src/api/http/__init__.py
@@ -0,0 +1 @@
+"""HTTP API"""
diff --git a/backend/service/auth-service/include/errors/exceptions.hpp b/backend/service/auth-service/src/api/http/routers.py
similarity index 100%
rename from backend/service/auth-service/include/errors/exceptions.hpp
rename to backend/service/auth-service/src/api/http/routers.py
diff --git a/backend/service/auth-service/include/infrastructure/kafka/consumer.hpp b/backend/service/auth-service/src/api/http/server.py
similarity index 100%
rename from backend/service/auth-service/include/infrastructure/kafka/consumer.hpp
rename to backend/service/auth-service/src/api/http/server.py
diff --git a/backend/service/auth-service/src/core/__init__.py b/backend/service/auth-service/src/core/__init__.py
new file mode 100644
index 0000000..1d0aeb6
--- /dev/null
+++ b/backend/service/auth-service/src/core/__init__.py
@@ -0,0 +1 @@
+"""Core module: configuration, security & exceptions"""
\ No newline at end of file
diff --git a/backend/service/auth-service/src/core/config.py b/backend/service/auth-service/src/core/config.py
new file mode 100644
index 0000000..2d787d9
--- /dev/null
+++ b/backend/service/auth-service/src/core/config.py
@@ -0,0 +1,61 @@
+from functools import lru_cache
+from pydantic import PostgresDsn, RedisDsn, field_validator
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+
+class Settings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_file=".env",
+ env_file_encoding="utf-8",
+ case_sensitive=False,
+ )
+
+ # App
+ APP_NAME: str = "auth-service"
+ APP_ENV: str = "development"
+ DEBUG: bool = False
+
+ # HTTP Server
+ HTTP_HOST: str = "0.0.0.0"
+ HTTP_PORT: int = 8000
+
+ # gRPC Server
+ GRPC_HOST: str = "0.0.0.0"
+ GRPC_PORT: int = 50051
+
+ # JWT
+ JWT_SECRET_KEY: str
+ JWT_ALGORITHM: str = "HS256"
+ JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
+ JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7
+
+ # PostgreSQL
+ POSTGRES_DSN: PostgresDsn
+
+ # Redis
+ REDIS_DSN: RedisDsn
+
+ # Kafka
+ KAFKA_BOOTSTRAP_SERVERS: str
+ KAFKA_TOPIC_USER_EVENTS: str = "user-events"
+ KAFKA_CONSUMER_GROUP_ID: str = "auth-service-group"
+
+ # Password hashing
+ BCRYPT_ROUNDS: int = 12
+
+ @field_validator("APP_ENV")
+ @classmethod
+ def validate_app_env(cls, v: str) -> str:
+ allowed = {"development", "staging", "production"}
+ if v not in allowed:
+ raise ValueError(f"APP_ENV must be one of {allowed}")
+ return v
+
+ @property
+ def is_production(self) -> bool:
+ return self.APP_ENV == "production"
+
+
+@lru_cache
+def get_settings() -> Settings:
+ return Settings()
\ No newline at end of file
diff --git a/backend/service/auth-service/src/core/exceptions.py b/backend/service/auth-service/src/core/exceptions.py
new file mode 100644
index 0000000..e223416
--- /dev/null
+++ b/backend/service/auth-service/src/core/exceptions.py
@@ -0,0 +1,70 @@
+from http import HTTPStatus
+
+
+class AppException(Exception):
+ """Base application exception."""
+
+ status_code: int = HTTPStatus.INTERNAL_SERVER_ERROR
+ detail: str = "An unexpected error occurred."
+
+ def __init__(self, detail: str | None = None) -> None:
+ self.detail = detail or self.__class__.detail
+ super().__init__(self.detail)
+
+
+# --- Auth ---
+
+class AuthException(AppException):
+ """Base auth exception."""
+
+
+class InvalidCredentialsError(AuthException):
+ status_code = HTTPStatus.UNAUTHORIZED
+ detail = "Invalid email or password."
+
+
+class InvalidTokenError(AuthException):
+ status_code = HTTPStatus.UNAUTHORIZED
+ detail = "Token is invalid or has expired."
+
+
+class TokenExpiredError(AuthException):
+ status_code = HTTPStatus.UNAUTHORIZED
+ detail = "Token has expired."
+
+
+class InsufficientPermissionsError(AuthException):
+ status_code = HTTPStatus.FORBIDDEN
+ detail = "You do not have permission to perform this action."
+
+
+# --- User ---
+
+class UserException(AppException):
+ """Base user exception."""
+
+
+class UserNotFoundError(UserException):
+ status_code = HTTPStatus.NOT_FOUND
+ detail = "User not found."
+
+
+class UserAlreadyExistsError(UserException):
+ status_code = HTTPStatus.CONFLICT
+ detail = "A user with this email already exists."
+
+
+class UserInactiveError(UserException):
+ status_code = HTTPStatus.FORBIDDEN
+ detail = "This user account is inactive."
+
+
+# --- Validation ---
+
+class ValidationException(AppException):
+ status_code = HTTPStatus.UNPROCESSABLE_ENTITY
+ detail = "Validation failed."
+
+
+class WeakPasswordError(ValidationException):
+ detail = "Password does not meet the minimum security requirements."
\ No newline at end of file
diff --git a/backend/service/auth-service/src/core/security.py b/backend/service/auth-service/src/core/security.py
new file mode 100644
index 0000000..d83c3be
--- /dev/null
+++ b/backend/service/auth-service/src/core/security.py
@@ -0,0 +1,91 @@
+from datetime import datetime, timedelta, timezone
+from typing import Any
+from uuid import UUID
+
+import bcrypt
+from jose import JWTError, jwt
+
+from src.core.config import get_settings
+from src.core.exceptions import InvalidTokenError, TokenExpiredError
+
+settings = get_settings()
+
+
+# PASSWORD HASHING
+def hash_password(plain: str) -> str:
+ """Return a bcrypt hash of *plain*."""
+ salt = bcrypt.gensalt(rounds=settings.BCRYPT_ROUNDS)
+ return bcrypt.hashpw(plain.encode(), salt).decode()
+
+
+def verify_password(plain: str, hashed: str) -> bool:
+ """Return True if *plain* matches *hashed*."""
+ return bcrypt.checkpw(plain.encode(), hashed.encode())
+
+
+# JWT
+_ACCESS = "access"
+_REFRESH = "refresh"
+
+
+def _create_token(
+ subject: str | UUID,
+ token_type: str,
+ expires_delta: timedelta,
+ extra_claims: dict[str, Any] | None = None,
+) -> str:
+ now = datetime.now(tz=timezone.utc)
+ payload: dict[str, Any] = {
+ "sub": str(subject),
+ "type": token_type,
+ "iat": now,
+ "exp": now + expires_delta,
+ }
+ if extra_claims:
+ payload.update(extra_claims)
+ return jwt.encode(payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
+
+
+def create_access_token(user_id: str | UUID, extra_claims: dict[str, Any] | None = None) -> str:
+ return _create_token(
+ subject=user_id,
+ token_type=_ACCESS,
+ expires_delta=timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES),
+ extra_claims=extra_claims,
+ )
+
+
+def create_refresh_token(user_id: str | UUID) -> str:
+ return _create_token(
+ subject=user_id,
+ token_type=_REFRESH,
+ expires_delta=timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS),
+ )
+
+
+def decode_token(token: str, expected_type: str = _ACCESS) -> dict[str, Any]:
+ """
+ Decode and validate *token*.
+ Raises:
+ TokenExpiredError: if the token is past its expiry.
+ InvalidTokenError: if the token is malformed or has an unexpected type.
+ """
+ try:
+ payload = jwt.decode(
+ token,
+ settings.JWT_SECRET_KEY,
+ algorithms=[settings.JWT_ALGORITHM],
+ )
+ except jwt.ExpiredSignatureError:
+ raise TokenExpiredError()
+ except JWTError:
+ raise InvalidTokenError()
+
+ if payload.get("type") != expected_type:
+ raise InvalidTokenError("Unexpected token type.")
+
+ return payload
+
+
+def decode_refresh_token(token: str) -> dict[str, Any]:
+ return decode_token(token, expected_type=_REFRESH)
diff --git a/backend/service/auth-service/src/domain/__init__.py b/backend/service/auth-service/src/domain/__init__.py
new file mode 100644
index 0000000..b57cce8
--- /dev/null
+++ b/backend/service/auth-service/src/domain/__init__.py
@@ -0,0 +1,5 @@
+"""Domain: Business entities"""
+
+from src.domain.entities import RefreshToken, User, UserRole, UserStatus
+
+__all__ = ["User", "UserRole", "UserStatus", "RefreshToken"]
\ No newline at end of file
diff --git a/backend/service/auth-service/src/domain/entities.py b/backend/service/auth-service/src/domain/entities.py
new file mode 100644
index 0000000..a4f0bcb
--- /dev/null
+++ b/backend/service/auth-service/src/domain/entities.py
@@ -0,0 +1,87 @@
+from dataclasses import dataclass, field
+from datetime import datetime, timezone
+from enum import StrEnum
+from uuid import UUID, uuid4
+
+
+class UserRole(StrEnum):
+ ADMIN = "admin"
+ USER = "user"
+ SERVICE = "service"
+
+
+class UserStatus(StrEnum):
+ ACTIVE = "active"
+ INACTIVE = "inactive"
+ BANNED = "banned"
+ PENDING_VERIFICATION = "pending_verification"
+
+
+@dataclass
+class User:
+ """Core user entity — framework-agnostic"""
+
+ email: str
+ hashed_password: str
+
+ id: UUID = field(default_factory=uuid4)
+ role: UserRole = UserRole.USER
+ status: UserStatus = UserStatus.PENDING_VERIFICATION
+ created_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
+ updated_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
+
+ # Optional profile fields
+ first_name: str | None = None
+ last_name: str | None = None
+
+ @property
+ def is_active(self) -> bool:
+ return self.status == UserStatus.ACTIVE
+
+ @property
+ def is_admin(self) -> bool:
+ return self.role == UserRole.ADMIN
+
+ @property
+ def full_name(self) -> str | None:
+ parts = filter(None, [self.first_name, self.last_name])
+ return " ".join(parts) or None
+
+ def activate(self) -> None:
+ self.status = UserStatus.ACTIVE
+ self._touch()
+
+ def deactivate(self) -> None:
+ self.status = UserStatus.INACTIVE
+ self._touch()
+
+ def ban(self) -> None:
+ self.status = UserStatus.BANNED
+ self._touch()
+
+ def _touch(self) -> None:
+ self.updated_at = datetime.now(tz=timezone.utc)
+
+
+@dataclass
+class RefreshToken:
+ """Persisted refresh token for rotation / revocation"""
+
+ token: str
+ user_id: UUID
+ expires_at: datetime
+
+ id: UUID = field(default_factory=uuid4)
+ created_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
+ revoked: bool = False
+
+ @property
+ def is_expired(self) -> bool:
+ return datetime.now(tz=timezone.utc) >= self.expires_at
+
+ @property
+ def is_valid(self) -> bool:
+ return not self.revoked and not self.is_expired
+
+ def revoke(self) -> None:
+ self.revoked = True
\ No newline at end of file
diff --git a/backend/service/auth-service/src/errors/exceptions.cpp b/backend/service/auth-service/src/errors/exceptions.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/infrastructure/__init__.py b/backend/service/auth-service/src/infrastructure/__init__.py
new file mode 100644
index 0000000..92ef1b0
--- /dev/null
+++ b/backend/service/auth-service/src/infrastructure/__init__.py
@@ -0,0 +1 @@
+"""Infrastructure: external services & storage"""
\ No newline at end of file
diff --git a/backend/service/auth-service/src/infrastructure/kafka/__init__.py b/backend/service/auth-service/src/infrastructure/kafka/__init__.py
new file mode 100644
index 0000000..030da62
--- /dev/null
+++ b/backend/service/auth-service/src/infrastructure/kafka/__init__.py
@@ -0,0 +1 @@
+"""Kafka: event streaming"""
\ No newline at end of file
diff --git a/backend/service/auth-service/src/infrastructure/kafka/consumer.cpp b/backend/service/auth-service/src/infrastructure/kafka/consumer.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/infrastructure/kafka/producer.hpp b/backend/service/auth-service/src/infrastructure/kafka/consumer.py
similarity index 100%
rename from backend/service/auth-service/include/infrastructure/kafka/producer.hpp
rename to backend/service/auth-service/src/infrastructure/kafka/consumer.py
diff --git a/backend/service/auth-service/src/infrastructure/kafka/producer.cpp b/backend/service/auth-service/src/infrastructure/kafka/producer.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/models/domain.hpp b/backend/service/auth-service/src/infrastructure/kafka/producer.py
similarity index 100%
rename from backend/service/auth-service/include/models/domain.hpp
rename to backend/service/auth-service/src/infrastructure/kafka/producer.py
diff --git a/backend/service/auth-service/src/infrastructure/postgres/__init__.py b/backend/service/auth-service/src/infrastructure/postgres/__init__.py
new file mode 100644
index 0000000..6e8ae75
--- /dev/null
+++ b/backend/service/auth-service/src/infrastructure/postgres/__init__.py
@@ -0,0 +1 @@
+"""PostgreSQL DB"""
\ No newline at end of file
diff --git a/backend/service/auth-service/include/models/domain_types.hpp b/backend/service/auth-service/src/infrastructure/postgres/database.py
similarity index 100%
rename from backend/service/auth-service/include/models/domain_types.hpp
rename to backend/service/auth-service/src/infrastructure/postgres/database.py
diff --git a/backend/service/auth-service/include/models/dto.hpp b/backend/service/auth-service/src/infrastructure/postgres/models.py
similarity index 100%
rename from backend/service/auth-service/include/models/dto.hpp
rename to backend/service/auth-service/src/infrastructure/postgres/models.py
diff --git a/backend/service/auth-service/src/infrastructure/redis/__init__.py b/backend/service/auth-service/src/infrastructure/redis/__init__.py
new file mode 100644
index 0000000..70b3ff9
--- /dev/null
+++ b/backend/service/auth-service/src/infrastructure/redis/__init__.py
@@ -0,0 +1 @@
+"""Redis client for caching & sessions"""
\ No newline at end of file
diff --git a/backend/service/auth-service/include/repository/postgres.hpp b/backend/service/auth-service/src/infrastructure/redis/client.py
similarity index 100%
rename from backend/service/auth-service/include/repository/postgres.hpp
rename to backend/service/auth-service/src/infrastructure/redis/client.py
diff --git a/backend/service/auth-service/src/main.cpp b/backend/service/auth-service/src/main.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/main.py b/backend/service/auth-service/src/main.py
new file mode 100644
index 0000000..ba9e7cd
--- /dev/null
+++ b/backend/service/auth-service/src/main.py
@@ -0,0 +1,145 @@
+import asyncio
+import logging
+from contextlib import asynccontextmanager
+from typing import AsyncIterator
+
+import uvicorn
+from fastapi import FastAPI, Request
+from fastapi.responses import JSONResponse
+
+from src import __version__
+from src.core.config import get_settings
+from src.core.exceptions import AppException
+
+logger = logging.getLogger(__name__)
+settings = get_settings()
+
+
+# Lifespan — startup / shutdown of shared resources
+
+@asynccontextmanager
+async def lifespan(app: FastAPI) -> AsyncIterator[None]:
+ """
+ Initialise infrastructure clients on startup and cleanly shut them down.
+ All resources are stored in app.state so routers and dependencies can
+ reach them without importing globals.
+ """
+ from src.infrastructure.postgres.database import Database
+ from src.infrastructure.redis.client import RedisClient
+ from src.infrastructure.kafka.producer import KafkaProducer
+ from src.infrastructure.kafka.consumer import KafkaConsumer
+
+ logger.info("Starting %s v%s [%s]", settings.APP_NAME, __version__, settings.APP_ENV)
+
+ # Postgres
+ db = Database(dsn=str(settings.POSTGRES_DSN))
+ await db.connect()
+ app.state.db = db
+
+ # Redis
+ redis = RedisClient(dsn=str(settings.REDIS_DSN))
+ await redis.connect()
+ app.state.redis = redis
+
+ # Kafka producer
+ producer = KafkaProducer(bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS)
+ await producer.start()
+ app.state.kafka_producer = producer
+
+ # Kafka consumer (runs as a background task)
+ consumer = KafkaConsumer(
+ bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS,
+ topic=settings.KAFKA_TOPIC_USER_EVENTS,
+ group_id=settings.KAFKA_CONSUMER_GROUP_ID,
+ )
+ consumer_task = asyncio.create_task(consumer.start(), name="kafka-consumer")
+ app.state.kafka_consumer = consumer
+ app.state.kafka_consumer_task = consumer_task
+
+ logger.info("All infrastructure clients ready.")
+
+ yield # ── application is running ──
+
+ logger.info("Shutting down %s…", settings.APP_NAME)
+
+ consumer_task.cancel()
+ try:
+ await consumer_task
+ except asyncio.CancelledError:
+ pass
+
+ await producer.stop()
+ await redis.disconnect()
+ await db.disconnect()
+
+ logger.info("Shutdown complete.")
+
+
+# FastAPI application factory
+def create_app() -> FastAPI:
+ app = FastAPI(
+ title=settings.APP_NAME,
+ version=__version__,
+ docs_url="/docs" if not settings.is_production else None,
+ redoc_url="/redoc" if not settings.is_production else None,
+ lifespan=lifespan,
+ )
+
+ # ── Exception handlers ──────────────────────────────────────────────────
+
+ @app.exception_handler(AppException)
+ async def app_exception_handler(request: Request, exc: AppException) -> JSONResponse:
+ return JSONResponse(
+ status_code=exc.status_code,
+ content={"detail": exc.detail},
+ )
+
+ # ── Routers ─────────────────────────────────────────────────────────────
+
+ from src.api.http.routers import router as http_router
+ app.include_router(http_router, prefix="/api/v1")
+
+ # ── Health check ────────────────────────────────────────────────────────
+
+ @app.get("/health", tags=["meta"], include_in_schema=False)
+ async def health() -> dict:
+ return {"status": "ok", "version": __version__}
+
+ return app
+
+
+# Entrypoint
+app = create_app()
+
+
+async def _serve_grpc() -> None:
+ """Start the gRPC server alongside the HTTP server."""
+ from src.api.grpc.server import GrpcServer
+ server = GrpcServer(host=settings.GRPC_HOST, port=settings.GRPC_PORT)
+ await server.start()
+ logger.info("gRPC server listening on %s:%s", settings.GRPC_HOST, settings.GRPC_PORT)
+ await server.wait_for_termination()
+
+
+async def _serve_http() -> None:
+ config = uvicorn.Config(
+ app=app,
+ host=settings.HTTP_HOST,
+ port=settings.HTTP_PORT,
+ log_level="debug" if settings.DEBUG else "info",
+ loop="none", # we manage the event loop ourselves
+ )
+ server = uvicorn.Server(config)
+ await server.serve()
+
+
+async def main() -> None:
+ logging.basicConfig(
+ level=logging.DEBUG if settings.DEBUG else logging.INFO,
+ format="%(asctime)s %(levelname)-8s %(name)s — %(message)s",
+ )
+ await asyncio.gather(_serve_http(), _serve_grpc())
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
\ No newline at end of file
diff --git a/backend/service/auth-service/src/repositories/__init__.py b/backend/service/auth-service/src/repositories/__init__.py
new file mode 100644
index 0000000..7653ca8
--- /dev/null
+++ b/backend/service/auth-service/src/repositories/__init__.py
@@ -0,0 +1 @@
+"""Repository: data access"""
\ No newline at end of file
diff --git a/backend/service/auth-service/include/repository/postgres/client.hpp b/backend/service/auth-service/src/repositories/base.py
similarity index 100%
rename from backend/service/auth-service/include/repository/postgres/client.hpp
rename to backend/service/auth-service/src/repositories/base.py
diff --git a/backend/service/auth-service/include/repository/postgres/queries.hpp b/backend/service/auth-service/src/repositories/user.py
similarity index 100%
rename from backend/service/auth-service/include/repository/postgres/queries.hpp
rename to backend/service/auth-service/src/repositories/user.py
diff --git a/backend/service/auth-service/src/repository/postgres.cpp b/backend/service/auth-service/src/repository/postgres.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/repository/postgres/client.cpp b/backend/service/auth-service/src/repository/postgres/client.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/repository/postgres/queries.cpp b/backend/service/auth-service/src/repository/postgres/queries.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/repository/redis/client.cpp b/backend/service/auth-service/src/repository/redis/client.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/schemes/__init__.py b/backend/service/auth-service/src/schemes/__init__.py
new file mode 100644
index 0000000..4ff7ea3
--- /dev/null
+++ b/backend/service/auth-service/src/schemes/__init__.py
@@ -0,0 +1,20 @@
+"""Schemes: dtos & data validation models"""
+
+from src.schemes.dto import (
+ AccessTokenResponse,
+ LoginRequest,
+ LogoutRequest,
+ RefreshRequest,
+ RegisterRequest,
+ RegisterResponse,
+ TokenPair,
+ TokenPayload,
+ UserCreatedEvent,
+ UserResponse,
+)
+
+__all__ = [
+ "RegisterRequest", "LoginRequest", "RefreshRequest", "LogoutRequest",
+ "TokenPair", "AccessTokenResponse", "RegisterResponse",
+ "UserResponse", "TokenPayload", "UserCreatedEvent",
+]
\ No newline at end of file
diff --git a/backend/service/auth-service/src/schemes/dto.py b/backend/service/auth-service/src/schemes/dto.py
new file mode 100644
index 0000000..63d11e2
--- /dev/null
+++ b/backend/service/auth-service/src/schemes/dto.py
@@ -0,0 +1,98 @@
+from datetime import datetime
+from uuid import UUID
+
+from pydantic import BaseModel, EmailStr, Field, field_validator
+
+from src.domain.entities import UserRole, UserStatus
+
+
+# SHARED BASE
+class CamelModel(BaseModel):
+ """Base model that serialises to camelCase for HTTP responses."""
+
+ model_config = {"populate_by_name": True, "use_enum_values": True}
+
+
+# AUTH REQUESTS
+class RegisterRequest(CamelModel):
+ email: EmailStr
+ password: str = Field(min_length=8, max_length=128)
+ first_name: str | None = Field(default=None, max_length=64)
+ last_name: str | None = Field(default=None, max_length=64)
+
+ @field_validator("password")
+ @classmethod
+ def password_not_blank(cls, v: str) -> str:
+ if v.strip() == "":
+ raise ValueError("Password must not be blank.")
+ return v
+
+
+class LoginRequest(CamelModel):
+ email: EmailStr
+ password: str
+
+
+class RefreshRequest(CamelModel):
+ refresh_token: str
+
+
+class LogoutRequest(CamelModel):
+ refresh_token: str
+
+
+# AUTH RESPONSES
+class TokenPair(CamelModel):
+ access_token: str
+ refresh_token: str
+ token_type: str = "bearer"
+ expires_in: int # seconds until access token expires
+
+
+class AccessTokenResponse(CamelModel):
+ access_token: str
+ token_type: str = "bearer"
+ expires_in: int
+
+
+# USER RESPONSES
+class UserResponse(CamelModel):
+ id: UUID
+ email: EmailStr
+ role: UserRole
+ status: UserStatus
+ first_name: str | None
+ last_name: str | None
+ created_at: datetime
+ updated_at: datetime
+
+ @property
+ def full_name(self) -> str | None:
+ parts = filter(None, [self.first_name, self.last_name])
+ return " ".join(parts) or None
+
+
+class RegisterResponse(CamelModel):
+ user: UserResponse
+ tokens: TokenPair
+
+
+# SERVICE-TO-SERVICE DTOS
+
+class TokenPayload(CamelModel):
+ """Decoded JWT payload passed between internal layers."""
+
+ sub: UUID
+ type: str
+ iat: datetime
+ exp: datetime
+ role: UserRole = UserRole.USER
+
+
+class UserCreatedEvent(CamelModel):
+ """Kafka event emitted after successful registration."""
+
+ event: str = "user.created"
+ user_id: UUID
+ email: EmailStr
+ occurred_at: datetime
\ No newline at end of file
diff --git a/backend/service/auth-service/src/service/logic.cpp b/backend/service/auth-service/src/service/logic.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/service/manager.cpp b/backend/service/auth-service/src/service/manager.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/service/validator.cpp b/backend/service/auth-service/src/service/validator.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/services/__init__.py b/backend/service/auth-service/src/services/__init__.py
new file mode 100644
index 0000000..a5a7f92
--- /dev/null
+++ b/backend/service/auth-service/src/services/__init__.py
@@ -0,0 +1,5 @@
+"""Services: business logic"""
+
+from src.services.auth import AuthService
+
+__all__ = ["AuthService"]
\ No newline at end of file
diff --git a/backend/service/auth-service/src/services/auth.py b/backend/service/auth-service/src/services/auth.py
new file mode 100644
index 0000000..dd7f300
--- /dev/null
+++ b/backend/service/auth-service/src/services/auth.py
@@ -0,0 +1,194 @@
+from datetime import datetime, timedelta, timezone
+from uuid import UUID
+
+from src.core.config import get_settings
+from src.core.exceptions import (
+ InvalidCredentialsError,
+ InvalidTokenError,
+ UserAlreadyExistsError,
+ UserInactiveError,
+ UserNotFoundError,
+)
+from src.core.security import (
+ create_access_token,
+ create_refresh_token,
+ decode_refresh_token,
+ hash_password,
+ verify_password,
+)
+from src.domain.entities import RefreshToken, User
+from src.schemes.dto import (
+ AccessTokenResponse,
+ LoginRequest,
+ RefreshRequest,
+ RegisterRequest,
+ RegisterResponse,
+ TokenPair,
+ UserCreatedEvent,
+ UserResponse,
+)
+from src.services.validators import validate_password_strength
+
+settings = get_settings()
+
+
+class AuthService:
+ """
+ Orchestrates authentication flows:
+ - register, login, token refresh, logout
+ Depends on abstract repository and infrastructure interfaces so it
+ remains fully unit-testable without real I/O.
+ """
+
+ def __init__(
+ self,
+ user_repo, # repositories.user.UserRepository
+ token_repo, # repositories.base.BaseRepository[RefreshToken]
+ redis_client, # infrastructure.redis.client.RedisClient
+ kafka_producer, # infrastructure.kafka.producer.KafkaProducer
+ ) -> None:
+ self._users = user_repo
+ self._tokens = token_repo
+ self._redis = redis_client
+ self._kafka = kafka_producer
+
+ # ------------------------------------------------------------------
+ # Public API
+ # ------------------------------------------------------------------
+
+ async def register(self, data: RegisterRequest) -> RegisterResponse:
+ """Create a new user account and return a token pair."""
+ await self._assert_email_available(data.email)
+ validate_password_strength(data.password)
+
+ user = User(
+ email=data.email,
+ hashed_password=hash_password(data.password),
+ first_name=data.first_name,
+ last_name=data.last_name,
+ )
+ user.activate()
+ await self._users.save(user)
+
+ tokens = await self._issue_token_pair(user)
+ await self._publish_user_created(user)
+
+ return RegisterResponse(
+ user=self._to_user_response(user),
+ tokens=tokens,
+ )
+
+ async def login(self, data: LoginRequest) -> TokenPair:
+ """Authenticate with email + password and return a token pair."""
+ user = await self._users.get_by_email(data.email)
+ if user is None or not verify_password(data.password, user.hashed_password):
+ raise InvalidCredentialsError()
+
+ if not user.is_active:
+ raise UserInactiveError()
+
+ return await self._issue_token_pair(user)
+
+ async def refresh(self, data: RefreshRequest) -> AccessTokenResponse:
+ """
+ Rotate refresh token and return a new access token.
+
+ Uses a one-time-use pattern: the old refresh token is revoked and a
+ new one is persisted in its place (refresh token rotation).
+ """
+ payload = decode_refresh_token(data.refresh_token)
+ user_id: UUID = UUID(payload["sub"])
+
+ stored: RefreshToken | None = await self._tokens.get_by_token(data.refresh_token)
+ if stored is None or not stored.is_valid:
+ # Possible token reuse attack — revoke all tokens for this user.
+ await self._tokens.revoke_all_for_user(user_id)
+ raise InvalidTokenError("Refresh token is invalid or has been revoked.")
+
+ user = await self._users.get_by_id(user_id)
+ if user is None:
+ raise UserNotFoundError()
+ if not user.is_active:
+ raise UserInactiveError()
+
+ stored.revoke()
+ await self._tokens.save(stored)
+
+ new_access = create_access_token(user.id, extra_claims={"role": user.role})
+ new_refresh = create_refresh_token(user.id)
+ await self._persist_refresh_token(user.id, new_refresh)
+
+ # Blacklist the old token in Redis (belt-and-suspenders)
+ await self._blacklist_token(data.refresh_token)
+
+ return AccessTokenResponse(
+ access_token=new_access,
+ expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60,
+ )
+
+ async def logout(self, refresh_token: str) -> None:
+ """Revoke the supplied refresh token."""
+ stored: RefreshToken | None = await self._tokens.get_by_token(refresh_token)
+ if stored is not None:
+ stored.revoke()
+ await self._tokens.save(stored)
+ await self._blacklist_token(refresh_token)
+
+ async def logout_all(self, user_id: UUID) -> None:
+ """Revoke all active sessions for *user_id*."""
+ await self._tokens.revoke_all_for_user(user_id)
+
+ # ------------------------------------------------------------------
+ # Private helpers
+ # ------------------------------------------------------------------
+
+ async def _assert_email_available(self, email: str) -> None:
+ existing = await self._users.get_by_email(email)
+ if existing is not None:
+ raise UserAlreadyExistsError()
+
+ async def _issue_token_pair(self, user: User) -> TokenPair:
+ access = create_access_token(user.id, extra_claims={"role": user.role})
+ refresh = create_refresh_token(user.id)
+ await self._persist_refresh_token(user.id, refresh)
+ return TokenPair(
+ access_token=access,
+ refresh_token=refresh,
+ expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60,
+ )
+
+ async def _persist_refresh_token(self, user_id: UUID, token: str) -> None:
+ expires_at = datetime.now(tz=timezone.utc) + timedelta(
+ days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS
+ )
+ record = RefreshToken(token=token, user_id=user_id, expires_at=expires_at)
+ await self._tokens.save(record)
+
+ async def _blacklist_token(self, token: str) -> None:
+ ttl = settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 86_400
+ await self._redis.setex(f"blacklist:{token}", ttl, "1")
+
+ async def _publish_user_created(self, user: User) -> None:
+ event = UserCreatedEvent(
+ user_id=user.id,
+ email=user.email,
+ occurred_at=user.created_at,
+ )
+ await self._kafka.produce(
+ topic=settings.KAFKA_TOPIC_USER_EVENTS,
+ key=str(user.id),
+ value=event.model_dump_json(),
+ )
+
+ @staticmethod
+ def _to_user_response(user: User) -> UserResponse:
+ return UserResponse(
+ id=user.id,
+ email=user.email,
+ role=user.role,
+ status=user.status,
+ first_name=user.first_name,
+ last_name=user.last_name,
+ created_at=user.created_at,
+ updated_at=user.updated_at,
+ )
diff --git a/backend/service/auth-service/src/services/validators.py b/backend/service/auth-service/src/services/validators.py
new file mode 100644
index 0000000..43a9f0f
--- /dev/null
+++ b/backend/service/auth-service/src/services/validators.py
@@ -0,0 +1,55 @@
+import re
+
+from src.core.exceptions import WeakPasswordError
+
+# At least: 1 uppercase, 1 lowercase, 1 digit, 1 special char
+_PASSWORD_RE = re.compile(
+ r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?]).{8,128}$"
+)
+
+_COMMON_PASSWORDS = {
+ "Password1!",
+ "Password123!",
+ "Qwerty123!",
+ "Welcome1!",
+ "Admin123!",
+}
+
+
+def validate_password_strength(password: str) -> None:
+ """
+ Enforce password strength rules.
+ Rules:
+ - 8–128 characters
+ - At least one uppercase letter
+ - At least one lowercase letter
+ - At least one digit
+ - At least one special character
+ - Not a commonly used password
+ Raises:
+ WeakPasswordError: when any rule is violated.
+ """
+ if password in _COMMON_PASSWORDS:
+ raise WeakPasswordError("This password is too common.")
+
+ if not _PASSWORD_RE.match(password):
+ raise WeakPasswordError(
+ "Password must be 8–128 characters and contain at least one uppercase letter, "
+ "one lowercase letter, one digit, and one special character."
+ )
+
+
+def validate_email_domain(email: str, blocked_domains: set[str] | None = None) -> None:
+ """
+ Optionally reject sign-ups from disposable / blocked email domains.
+ Raises:
+ ValidationException: when the email domain is blocked.
+ """
+ if not blocked_domains:
+ return
+
+ from src.core.exceptions import ValidationException
+
+ domain = email.split("@")[-1].lower()
+ if domain in blocked_domains:
+ raise ValidationException(f"Sign-ups from '{domain}' are not allowed.")
diff --git a/backend/service/auth-service/src/utils/argon2_hasher.cpp b/backend/service/auth-service/src/utils/argon2_hasher.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/utils/config_parser.cpp b/backend/service/auth-service/src/utils/config_parser.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/src/utils/jwt_signer.cpp b/backend/service/auth-service/src/utils/jwt_signer.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/service/auth-service/include/repository/redis/client.hpp b/backend/service/auth-service/tests/__init__.py
similarity index 100%
rename from backend/service/auth-service/include/repository/redis/client.hpp
rename to backend/service/auth-service/tests/__init__.py
diff --git a/backend/service/auth-service/tests/conftest.py b/backend/service/auth-service/tests/conftest.py
new file mode 100644
index 0000000..1776c4a
--- /dev/null
+++ b/backend/service/auth-service/tests/conftest.py
@@ -0,0 +1,221 @@
+"""
+Shared pytest fixtures for unit and integration tests.
+Fixture scopes at a glance:
+ - session : real DB / Redis / Kafka connections (integration only)
+ - function : in-memory fakes and mocked services (unit tests, default)
+"""
+
+from __future__ import annotations
+
+import asyncio
+from datetime import datetime, timedelta, timezone
+from typing import AsyncIterator
+from unittest.mock import AsyncMock
+from uuid import uuid4
+
+import pytest
+import pytest_asyncio
+
+from src.core.config import Settings
+from src.domain.entities import RefreshToken, User, UserRole, UserStatus
+from src.schemes.dto import LoginRequest, RefreshRequest, RegisterRequest
+
+
+# EVENT LOOP
+@pytest.fixture(scope="session")
+def event_loop():
+ """Single event loop for the whole test session."""
+ policy = asyncio.get_event_loop_policy()
+ loop = policy.new_event_loop()
+ yield loop
+ loop.close()
+
+
+# SETTINGS OVERRIDE — ALWAYS USE TEST VALUES, NEVER PRODUCTION SECRETS
+@pytest.fixture(scope="session")
+def test_settings() -> Settings:
+ return Settings(
+ APP_ENV="development",
+ DEBUG=True,
+ JWT_SECRET_KEY="test-secret-key-not-for-production",
+ JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15,
+ JWT_REFRESH_TOKEN_EXPIRE_DAYS=1,
+ POSTGRES_DSN="postgresql+asyncpg://postgres:postgres@localhost:5432/auth_test",
+ REDIS_DSN="redis://localhost:6379/1",
+ KAFKA_BOOTSTRAP_SERVERS="localhost:9092",
+ BCRYPT_ROUNDS=4, # fast rounds for tests
+ )
+
+
+# DOMAIN OBJECT FACTORIES
+@pytest.fixture
+def make_user():
+ """Factory fixture — call it to get a ready-to-use User domain object."""
+
+ def _factory(
+ email: str = "user@example.com",
+ password_hash: str = "$2b$04$testhashvalue",
+ role: UserRole = UserRole.USER,
+ status: UserStatus = UserStatus.ACTIVE,
+ first_name: str | None = "Jane",
+ last_name: str | None = "Doe",
+ ) -> User:
+ user = User(
+ id=uuid4(),
+ email=email,
+ hashed_password=password_hash,
+ role=role,
+ status=status,
+ first_name=first_name,
+ last_name=last_name,
+ )
+ return user
+
+ return _factory
+
+
+@pytest.fixture
+def active_user(make_user) -> User:
+ return make_user(status=UserStatus.ACTIVE)
+
+
+@pytest.fixture
+def inactive_user(make_user) -> User:
+ return make_user(status=UserStatus.INACTIVE)
+
+
+@pytest.fixture
+def admin_user(make_user) -> User:
+ return make_user(role=UserRole.ADMIN, status=UserStatus.ACTIVE)
+
+
+@pytest.fixture
+def make_refresh_token(active_user):
+ """Factory for RefreshToken domain objects."""
+
+ def _factory(
+ user: User | None = None,
+ token: str = "some.refresh.token",
+ revoked: bool = False,
+ expires_in_days: int = 7,
+ ) -> RefreshToken:
+ rt = RefreshToken(
+ token=token,
+ user_id=(user or active_user).id,
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(days=expires_in_days),
+ revoked=revoked,
+ )
+ return rt
+
+ return _factory
+
+
+@pytest.fixture
+def valid_refresh_token(make_refresh_token) -> RefreshToken:
+ return make_refresh_token()
+
+
+@pytest.fixture
+def expired_refresh_token(make_refresh_token) -> RefreshToken:
+ return make_refresh_token(expires_in_days=-1)
+
+
+@pytest.fixture
+def revoked_refresh_token(make_refresh_token) -> RefreshToken:
+ return make_refresh_token(revoked=True)
+
+
+# REQUEST DTO FIXTURES
+@pytest.fixture
+def register_request() -> RegisterRequest:
+ return RegisterRequest(
+ email="newuser@example.com",
+ password="Str0ng!Pass",
+ first_name="New",
+ last_name="User",
+ )
+
+
+@pytest.fixture
+def login_request() -> LoginRequest:
+ return LoginRequest(email="user@example.com", password="Str0ng!Pass")
+
+
+@pytest.fixture
+def refresh_request(valid_refresh_token) -> RefreshRequest:
+ return RefreshRequest(refresh_token=valid_refresh_token.token)
+
+
+# INFRASTRUCTURE MOCKS (UNIT TESTS)
+@pytest.fixture
+def mock_user_repo():
+ repo = AsyncMock()
+ repo.get_by_email.return_value = None
+ repo.get_by_id.return_value = None
+ repo.save.return_value = None
+ return repo
+
+
+@pytest.fixture
+def mock_token_repo():
+ repo = AsyncMock()
+ repo.get_by_token.return_value = None
+ repo.save.return_value = None
+ repo.revoke_all_for_user.return_value = None
+ return repo
+
+
+@pytest.fixture
+def mock_redis():
+ client = AsyncMock()
+ client.setex.return_value = True
+ client.get.return_value = None
+ return client
+
+
+@pytest.fixture
+def mock_kafka_producer():
+ producer = AsyncMock()
+ producer.produce.return_value = None
+ return producer
+
+
+# AUTHSERVICE (UNIT TESTS — ALL DEPS ARE MOCKS)
+@pytest.fixture
+def auth_service(mock_user_repo, mock_token_repo, mock_redis, mock_kafka_producer):
+ from src.services.auth import AuthService
+
+ return AuthService(
+ user_repo=mock_user_repo,
+ token_repo=mock_token_repo,
+ redis_client=mock_redis,
+ kafka_producer=mock_kafka_producer,
+ )
+
+
+# FASTAPI TEST CLIENT (INTEGRATION TESTS)
+@pytest_asyncio.fixture(scope="session")
+async def test_app(test_settings):
+ """
+ Full FastAPI app wired to real infrastructure.
+ Requires running Postgres, Redis, and Kafka (e.g. via docker-compose).
+ """
+ from unittest.mock import patch
+
+ with patch("src.core.config.get_settings", return_value=test_settings):
+ from src.main import create_app
+ application = create_app()
+
+ async with application.router.lifespan_context(application):
+ yield application
+
+
+@pytest_asyncio.fixture
+async def client(test_app) -> AsyncIterator:
+ from httpx import AsyncClient, ASGITransport
+
+ async with AsyncClient(
+ transport=ASGITransport(app=test_app),
+ base_url="http://testserver",
+ ) as ac:
+ yield ac
diff --git a/backend/service/auth-service/tests/integration.py b/backend/service/auth-service/tests/integration.py
new file mode 100644
index 0000000..def8bb9
--- /dev/null
+++ b/backend/service/auth-service/tests/integration.py
@@ -0,0 +1,288 @@
+"""
+Integration tests — exercise the full HTTP stack against real infrastructure.
+Prerequisites (e.g. via docker-compose):
+ - PostgreSQL on localhost:5432 (db: auth_test) ЭТИ ХОСТЫ НЕ Я ПРИДУМЫВАЛ
+ - Redis on localhost:6379 (db: 1) ЭТИ ХОСТЫ НЕ Я ПРИДУМЫВАЛ
+ - Kafka on localhost:9092 ЭТИ ХОСТЫ НЕ Я ПРИДУМЫВАЛ
+Run only integration tests:
+ pytest tests/integration.py -m integration
+All tests are marked with @pytest.mark.integration so they can be excluded
+from fast CI runs with: pytest -m "not integration"
+"""
+
+from __future__ import annotations
+
+import pytest
+from httpx import AsyncClient
+
+pytestmark = pytest.mark.integration
+
+
+# HELPERS
+_BASE = "/api/v1"
+
+_VALID_USER = {
+ "email": "integration@example.com",
+ "password": "Integr@tion1",
+ "firstName": "Integration",
+ "lastName": "Test",
+}
+
+_OTHER_USER = {
+ "email": "other@example.com",
+ "password": "0th3rUs3r!",
+}
+
+
+async def _register(client: AsyncClient, payload: dict | None = None) -> dict:
+ resp = await client.post(f"{_BASE}/auth/register", json=payload or _VALID_USER)
+ assert resp.status_code == 201, resp.text
+ return resp.json()
+
+
+async def _login(client: AsyncClient, payload: dict | None = None) -> dict:
+ body = payload or {"email": _VALID_USER["email"], "password": _VALID_USER["password"]}
+ resp = await client.post(f"{_BASE}/auth/login", json=body)
+ assert resp.status_code == 200, resp.text
+ return resp.json()
+
+
+# HEALTH
+@pytest.mark.asyncio
+async def test_health_endpoint(client: AsyncClient):
+ resp = await client.get("/health")
+ assert resp.status_code == 200
+ body = resp.json()
+ assert body["status"] == "ok"
+ assert "version" in body
+
+
+# POST /auth/register
+@pytest.mark.asyncio
+class TestRegister:
+ async def test_register_success(self, client: AsyncClient):
+ resp = await client.post(f"{_BASE}/auth/register", json=_VALID_USER)
+ assert resp.status_code == 201
+ body = resp.json()
+ assert body["user"]["email"] == _VALID_USER["email"]
+ assert "accessToken" in body["tokens"]
+ assert "refreshToken" in body["tokens"]
+
+ async def test_register_duplicate_email_returns_409(self, client: AsyncClient):
+ await _register(client)
+ resp = await client.post(f"{_BASE}/auth/register", json=_VALID_USER)
+ assert resp.status_code == 409
+
+ async def test_register_invalid_email_returns_422(self, client: AsyncClient):
+ payload = {**_VALID_USER, "email": "not-an-email"}
+ resp = await client.post(f"{_BASE}/auth/register", json=payload)
+ assert resp.status_code == 422
+
+ async def test_register_weak_password_returns_422(self, client: AsyncClient):
+ payload = {**_VALID_USER, "email": "weak@example.com", "password": "weak"}
+ resp = await client.post(f"{_BASE}/auth/register", json=payload)
+ assert resp.status_code == 422
+
+ async def test_register_missing_email_returns_422(self, client: AsyncClient):
+ payload = {"password": "Str0ng!Pass"}
+ resp = await client.post(f"{_BASE}/auth/register", json=payload)
+ assert resp.status_code == 422
+
+ async def test_register_missing_password_returns_422(self, client: AsyncClient):
+ payload = {"email": "nopass@example.com"}
+ resp = await client.post(f"{_BASE}/auth/register", json=payload)
+ assert resp.status_code == 422
+
+ async def test_register_response_has_no_password_field(self, client: AsyncClient):
+ body = await _register(client, {**_VALID_USER, "email": "safe@example.com"})
+ user_keys = body["user"].keys()
+ assert "password" not in user_keys
+ assert "hashedPassword" not in user_keys
+
+ async def test_register_token_type_is_bearer(self, client: AsyncClient):
+ body = await _register(client, {**_VALID_USER, "email": "bearer@example.com"})
+ assert body["tokens"]["tokenType"] == "bearer"
+
+
+# POST /auth/login
+@pytest.mark.asyncio
+class TestLogin:
+ async def test_login_success(self, client: AsyncClient):
+ await _register(client)
+ resp = await client.post(
+ f"{_BASE}/auth/login",
+ json={"email": _VALID_USER["email"], "password": _VALID_USER["password"]},
+ )
+ assert resp.status_code == 200
+ body = resp.json()
+ assert "accessToken" in body
+ assert "refreshToken" in body
+
+ async def test_login_wrong_password_returns_401(self, client: AsyncClient):
+ await _register(client)
+ resp = await client.post(
+ f"{_BASE}/auth/login",
+ json={"email": _VALID_USER["email"], "password": "Wr0ng!Pass"},
+ )
+ assert resp.status_code == 401
+
+ async def test_login_unknown_email_returns_401(self, client: AsyncClient):
+ resp = await client.post(
+ f"{_BASE}/auth/login",
+ json={"email": "ghost@example.com", "password": "Str0ng!Pass"},
+ )
+ assert resp.status_code == 401
+
+ async def test_login_returns_expires_in(self, client: AsyncClient):
+ await _register(client)
+ body = await _login(client)
+ assert isinstance(body["expiresIn"], int)
+ assert body["expiresIn"] > 0
+
+ async def test_login_missing_fields_returns_422(self, client: AsyncClient):
+ resp = await client.post(f"{_BASE}/auth/login", json={"email": "x@x.com"})
+ assert resp.status_code == 422
+
+
+# POST /auth/refresh
+@pytest.mark.asyncio
+class TestRefresh:
+ async def test_refresh_returns_new_access_token(self, client: AsyncClient):
+ await _register(client)
+ tokens = await _login(client)
+
+ resp = await client.post(
+ f"{_BASE}/auth/refresh",
+ json={"refreshToken": tokens["refreshToken"]},
+ )
+ assert resp.status_code == 200
+ body = resp.json()
+ assert "accessToken" in body
+ # New access token must differ from the original
+ assert body["accessToken"] != tokens["accessToken"]
+
+ async def test_refresh_token_rotation(self, client: AsyncClient):
+ """Using the same refresh token twice must fail on the second attempt."""
+ await _register(client, {**_VALID_USER, "email": "rotation@example.com"})
+ tokens = await _login(client, {"email": "rotation@example.com", "password": _VALID_USER["password"]})
+ refresh_token = tokens["refreshToken"]
+
+ # First use — valid
+ resp1 = await client.post(
+ f"{_BASE}/auth/refresh", json={"refreshToken": refresh_token}
+ )
+ assert resp1.status_code == 200
+
+ # Second use — must be rejected (token was rotated)
+ resp2 = await client.post(
+ f"{_BASE}/auth/refresh", json={"refreshToken": refresh_token}
+ )
+ assert resp2.status_code == 401
+
+ async def test_refresh_with_invalid_token_returns_401(self, client: AsyncClient):
+ resp = await client.post(
+ f"{_BASE}/auth/refresh", json={"refreshToken": "not.a.real.token"}
+ )
+ assert resp.status_code == 401
+
+ async def test_refresh_with_access_token_returns_401(self, client: AsyncClient):
+ await _register(client)
+ tokens = await _login(client)
+ # Pass the access token where a refresh token is expected
+ resp = await client.post(
+ f"{_BASE}/auth/refresh", json={"refreshToken": tokens["accessToken"]}
+ )
+ assert resp.status_code == 401
+
+ async def test_refresh_missing_token_returns_422(self, client: AsyncClient):
+ resp = await client.post(f"{_BASE}/auth/refresh", json={})
+ assert resp.status_code == 422
+
+
+# POST /auth/logout
+@pytest.mark.asyncio
+class TestLogout:
+ async def test_logout_success(self, client: AsyncClient):
+ await _register(client)
+ tokens = await _login(client)
+
+ resp = await client.post(
+ f"{_BASE}/auth/logout",
+ json={"refreshToken": tokens["refreshToken"]},
+ )
+ assert resp.status_code == 204
+
+ async def test_logout_invalidates_refresh_token(self, client: AsyncClient):
+ await _register(client, {**_VALID_USER, "email": "logout@example.com"})
+ tokens = await _login(client, {"email": "logout@example.com", "password": _VALID_USER["password"]})
+ refresh_token = tokens["refreshToken"]
+
+ await client.post(f"{_BASE}/auth/logout", json={"refreshToken": refresh_token})
+
+ # Subsequent refresh with the now-revoked token must fail
+ resp = await client.post(
+ f"{_BASE}/auth/refresh", json={"refreshToken": refresh_token}
+ )
+ assert resp.status_code == 401
+
+ async def test_logout_unknown_token_still_returns_204(self, client: AsyncClient):
+ """Logout is idempotent — unknown tokens are silently accepted."""
+ resp = await client.post(
+ f"{_BASE}/auth/logout", json={"refreshToken": "unknown.token.value"}
+ )
+ assert resp.status_code == 204
+
+
+# GET /auth/me
+@pytest.mark.asyncio
+class TestProtectedRoute:
+ async def test_me_returns_current_user(self, client: AsyncClient):
+ await _register(client)
+ tokens = await _login(client)
+
+ resp = await client.get(
+ f"{_BASE}/auth/me",
+ headers={"Authorization": f"Bearer {tokens['accessToken']}"},
+ )
+ assert resp.status_code == 200
+ assert resp.json()["email"] == _VALID_USER["email"]
+
+ async def test_me_without_token_returns_401(self, client: AsyncClient):
+ resp = await client.get(f"{_BASE}/auth/me")
+ assert resp.status_code == 401
+
+ async def test_me_with_expired_token_returns_401(self, client: AsyncClient):
+ from datetime import timedelta
+ from jose import jwt as jose_jwt
+ from src.core.config import get_settings
+
+ settings = get_settings()
+ past = __import__("datetime").datetime.now(tz=__import__("datetime").timezone.utc) - timedelta(seconds=1)
+ expired = jose_jwt.encode(
+ {"sub": str(__import__("uuid").uuid4()), "type": "access", "exp": past},
+ settings.JWT_SECRET_KEY,
+ algorithm=settings.JWT_ALGORITHM,
+ )
+ resp = await client.get(
+ f"{_BASE}/auth/me",
+ headers={"Authorization": f"Bearer {expired}"},
+ )
+ assert resp.status_code == 401
+
+ async def test_me_with_refresh_token_returns_401(self, client: AsyncClient):
+ """A refresh JWT must be rejected on an access-token-protected route."""
+ await _register(client)
+ tokens = await _login(client)
+
+ resp = await client.get(
+ f"{_BASE}/auth/me",
+ headers={"Authorization": f"Bearer {tokens['refreshToken']}"},
+ )
+ assert resp.status_code == 401
+
+ async def test_me_with_malformed_header_returns_401(self, client: AsyncClient):
+ resp = await client.get(
+ f"{_BASE}/auth/me",
+ headers={"Authorization": "NotBearer token"},
+ )
+ assert resp.status_code == 401
\ No newline at end of file
diff --git a/backend/service/auth-service/tests/unit.py b/backend/service/auth-service/tests/unit.py
new file mode 100644
index 0000000..68c43e9
--- /dev/null
+++ b/backend/service/auth-service/tests/unit.py
@@ -0,0 +1,544 @@
+"""
+Unit tests — no real I/O, all infrastructure is mocked.
+Coverage targets:
+ - src/core/security.py : JWT creation / decoding, password hashing
+ - src/core/exceptions.py : exception hierarchy and attributes
+ - src/domain/entities.py : User and RefreshToken domain logic
+ - src/services/validators.py: password strength and email-domain rules
+ - src/services/auth.py : AuthService flows (register, login, refresh, logout)
+"""
+
+from __future__ import annotations
+
+from datetime import datetime, timedelta, timezone
+from unittest.mock import AsyncMock, patch
+from uuid import uuid4
+
+import pytest
+
+from src.core.exceptions import (
+ InvalidCredentialsError,
+ InvalidTokenError,
+ TokenExpiredError,
+ UserAlreadyExistsError,
+ UserInactiveError,
+ WeakPasswordError,
+)
+from src.core.security import (
+ create_access_token,
+ create_refresh_token,
+ decode_refresh_token,
+ decode_token,
+ hash_password,
+ verify_password,
+)
+from src.domain.entities import RefreshToken, User, UserRole, UserStatus
+from src.schemes.dto import LoginRequest, RefreshRequest, RegisterRequest
+from src.services.validators import validate_email_domain, validate_password_strength
+
+
+# core/security.py
+class TestPasswordHashing:
+ def test_hash_returns_string(self):
+ assert isinstance(hash_password("Str0ng!Pass"), str)
+
+ def test_hash_is_not_plaintext(self):
+ assert hash_password("Str0ng!Pass") != "Str0ng!Pass"
+
+ def test_two_hashes_differ(self):
+ # bcrypt salts must differ between calls
+ assert hash_password("Str0ng!Pass") != hash_password("Str0ng!Pass")
+
+ def test_verify_correct_password(self):
+ hashed = hash_password("Str0ng!Pass")
+ assert verify_password("Str0ng!Pass", hashed) is True
+
+ def test_verify_wrong_password(self):
+ hashed = hash_password("Str0ng!Pass")
+ assert verify_password("WrongPass1!", hashed) is False
+
+ def test_verify_empty_string_fails(self):
+ hashed = hash_password("Str0ng!Pass")
+ assert verify_password("", hashed) is False
+
+
+class TestJWT:
+ def test_access_token_is_string(self):
+ token = create_access_token(uuid4())
+ assert isinstance(token, str)
+ assert len(token.split(".")) == 3 # header.payload.signature
+
+ def test_refresh_token_is_string(self):
+ token = create_refresh_token(uuid4())
+ assert isinstance(token, str)
+
+ def test_decode_access_token_returns_subject(self):
+ user_id = uuid4()
+ token = create_access_token(user_id)
+ payload = decode_token(token)
+ assert payload["sub"] == str(user_id)
+
+ def test_decode_access_token_type(self):
+ token = create_access_token(uuid4())
+ payload = decode_token(token)
+ assert payload["type"] == "access"
+
+ def test_decode_refresh_token_type(self):
+ token = create_refresh_token(uuid4())
+ payload = decode_refresh_token(token)
+ assert payload["type"] == "refresh"
+
+ def test_extra_claims_are_included(self):
+ token = create_access_token(uuid4(), extra_claims={"role": "admin"})
+ payload = decode_token(token)
+ assert payload["role"] == "admin"
+
+ def test_access_token_rejected_as_refresh(self):
+ token = create_access_token(uuid4())
+ with pytest.raises(InvalidTokenError):
+ decode_refresh_token(token)
+
+ def test_refresh_token_rejected_as_access(self):
+ token = create_refresh_token(uuid4())
+ with pytest.raises(InvalidTokenError):
+ decode_token(token)
+
+ def test_tampered_token_raises_invalid(self):
+ token = create_access_token(uuid4())
+ tampered = token[:-4] + "xxxx"
+ with pytest.raises(InvalidTokenError):
+ decode_token(tampered)
+
+ def test_expired_token_raises_expired(self):
+ from jose import jwt as jose_jwt
+ from src.core.config import get_settings
+ settings = get_settings()
+
+ past = datetime.now(tz=timezone.utc) - timedelta(seconds=1)
+ payload = {"sub": str(uuid4()), "type": "access", "exp": past}
+ expired_token = jose_jwt.encode(
+ payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM
+ )
+ with pytest.raises(TokenExpiredError):
+ decode_token(expired_token)
+
+ def test_garbage_string_raises_invalid(self):
+ with pytest.raises(InvalidTokenError):
+ decode_token("not.a.token")
+
+
+# core/exceptions.py
+class TestExceptions:
+ def test_default_detail_used_when_none_given(self):
+ exc = InvalidCredentialsError()
+ assert exc.detail == "Invalid email or password."
+
+ def test_custom_detail_overrides_default(self):
+ exc = InvalidTokenError("Custom message.")
+ assert exc.detail == "Custom message."
+
+ def test_exception_is_str_representable(self):
+ exc = UserAlreadyExistsError()
+ assert str(exc) == exc.detail
+
+ def test_status_codes(self):
+ from http import HTTPStatus
+ assert InvalidCredentialsError.status_code == HTTPStatus.UNAUTHORIZED
+ assert UserAlreadyExistsError.status_code == HTTPStatus.CONFLICT
+ assert UserInactiveError.status_code == HTTPStatus.FORBIDDEN
+ assert WeakPasswordError.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
+
+
+# domain/entities.py — User
+class TestUserEntity:
+ def test_default_status_is_pending(self):
+ user = User(email="a@b.com", hashed_password="hash")
+ assert user.status == UserStatus.PENDING_VERIFICATION
+
+ def test_activate_sets_active(self):
+ user = User(email="a@b.com", hashed_password="hash")
+ user.activate()
+ assert user.is_active is True
+ assert user.status == UserStatus.ACTIVE
+
+ def test_deactivate_sets_inactive(self):
+ user = User(email="a@b.com", hashed_password="hash")
+ user.activate()
+ user.deactivate()
+ assert user.is_active is False
+ assert user.status == UserStatus.INACTIVE
+
+ def test_ban_sets_banned(self):
+ user = User(email="a@b.com", hashed_password="hash")
+ user.ban()
+ assert user.status == UserStatus.BANNED
+ assert user.is_active is False
+
+ def test_activate_updates_updated_at(self):
+ user = User(email="a@b.com", hashed_password="hash")
+ before = user.updated_at
+ user.activate()
+ assert user.updated_at >= before
+
+ def test_is_admin_true_for_admin_role(self):
+ user = User(email="a@b.com", hashed_password="hash", role=UserRole.ADMIN)
+ assert user.is_admin is True
+
+ def test_is_admin_false_for_user_role(self):
+ user = User(email="a@b.com", hashed_password="hash", role=UserRole.USER)
+ assert user.is_admin is False
+
+ def test_full_name_both_parts(self):
+ user = User(
+ email="a@b.com", hashed_password="h",
+ first_name="Jane", last_name="Doe",
+ )
+ assert user.full_name == "Jane Doe"
+
+ def test_full_name_only_first(self):
+ user = User(email="a@b.com", hashed_password="h", first_name="Jane")
+ assert user.full_name == "Jane"
+
+ def test_full_name_none_when_empty(self):
+ user = User(email="a@b.com", hashed_password="h")
+ assert user.full_name is None
+
+ def test_unique_ids_by_default(self):
+ u1 = User(email="a@b.com", hashed_password="h")
+ u2 = User(email="b@b.com", hashed_password="h")
+ assert u1.id != u2.id
+
+
+# domain/entities.py — RefreshToken
+class TestRefreshTokenEntity:
+ def _make(self, *, days: int = 7, revoked: bool = False) -> RefreshToken:
+ return RefreshToken(
+ token="tok",
+ user_id=uuid4(),
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(days=days),
+ revoked=revoked,
+ )
+
+ def test_valid_token_is_valid(self):
+ assert self._make().is_valid is True
+
+ def test_expired_token_is_not_valid(self):
+ assert self._make(days=-1).is_valid is False
+
+ def test_revoked_token_is_not_valid(self):
+ assert self._make(revoked=True).is_valid is False
+
+ def test_revoke_sets_flag(self):
+ rt = self._make()
+ rt.revoke()
+ assert rt.revoked is True
+ assert rt.is_valid is False
+
+ def test_is_expired_false_for_future(self):
+ assert self._make(days=1).is_expired is False
+
+ def test_is_expired_true_for_past(self):
+ assert self._make(days=-1).is_expired is True
+
+
+# services/validators.py
+class TestPasswordValidator:
+ @pytest.mark.parametrize("pw", [
+ "Str0ng!Pass",
+ "C0mpl3x@Word",
+ "V3ryS3cur3#",
+ "A1b2C3d4!",
+ ])
+ def test_valid_passwords_pass(self, pw: str):
+ validate_password_strength(pw) # should not raise
+
+ @pytest.mark.parametrize("pw,reason", [
+ ("short1A!", "too short"),
+ ("alllowercase1!", "no uppercase"),
+ ("ALLUPPERCASE1!", "no lowercase"),
+ ("NoDigitsHere!", "no digit"),
+ ("NoSpecial1Char", "no special char"),
+ ("Password1!", "common password"),
+ ])
+ def test_invalid_passwords_raise(self, pw: str, reason: str):
+ with pytest.raises(WeakPasswordError, match=""), \
+ pytest.raises(WeakPasswordError):
+ validate_password_strength(pw)
+
+ def test_common_password_specific_message(self):
+ with pytest.raises(WeakPasswordError, match="too common"):
+ validate_password_strength("Password1!")
+
+ def test_password_at_max_length_passes(self):
+ # 128 chars, meets all rules
+ pw = "Aa1!" + "x" * 124
+ validate_password_strength(pw)
+
+ def test_password_over_max_length_fails(self):
+ pw = "Aa1!" + "x" * 125 # 129 chars
+ with pytest.raises(WeakPasswordError):
+ validate_password_strength(pw)
+
+
+class TestEmailDomainValidator:
+ def test_no_blocked_domains_passes(self):
+ validate_email_domain("user@any.com", blocked_domains=None)
+
+ def test_allowed_domain_passes(self):
+ validate_email_domain("user@gmail.com", blocked_domains={"tempmail.com"})
+
+ def test_blocked_domain_raises(self):
+ from src.core.exceptions import ValidationException
+ with pytest.raises(ValidationException):
+ validate_email_domain("user@tempmail.com", blocked_domains={"tempmail.com"})
+
+ def test_domain_check_is_case_insensitive(self):
+ from src.core.exceptions import ValidationException
+ with pytest.raises(ValidationException):
+ validate_email_domain("user@TempMail.COM", blocked_domains={"tempmail.com"})
+
+
+# services/auth.py — AuthService
+@pytest.mark.asyncio
+class TestAuthServiceRegister:
+ async def test_register_returns_user_and_tokens(
+ self, auth_service, register_request, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ result = await auth_service.register(register_request)
+
+ assert result.user.email == register_request.email
+ assert result.tokens.access_token
+ assert result.tokens.refresh_token
+
+ async def test_register_saves_user(
+ self, auth_service, register_request, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ await auth_service.register(register_request)
+ mock_user_repo.save.assert_awaited_once()
+
+ async def test_register_persists_refresh_token(
+ self, auth_service, register_request, mock_token_repo
+ ):
+ await auth_service.register(register_request)
+ mock_token_repo.save.assert_awaited()
+
+ async def test_register_publishes_kafka_event(
+ self, auth_service, register_request, mock_kafka_producer
+ ):
+ await auth_service.register(register_request)
+ mock_kafka_producer.produce.assert_awaited_once()
+
+ async def test_register_raises_if_email_taken(
+ self, auth_service, register_request, mock_user_repo, active_user
+ ):
+ mock_user_repo.get_by_email.return_value = active_user
+ with pytest.raises(UserAlreadyExistsError):
+ await auth_service.register(register_request)
+
+ async def test_register_raises_on_weak_password(
+ self, auth_service, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ weak_req = RegisterRequest(email="x@example.com", password="weakpass")
+ with pytest.raises(WeakPasswordError):
+ await auth_service.register(weak_req)
+
+ async def test_register_stores_hashed_password(
+ self, auth_service, register_request, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ await auth_service.register(register_request)
+
+ saved_user: User = mock_user_repo.save.call_args[0][0]
+ assert saved_user.hashed_password != register_request.password
+
+ async def test_register_activates_user(
+ self, auth_service, register_request, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ result = await auth_service.register(register_request)
+ assert result.user.status == UserStatus.ACTIVE
+
+
+@pytest.mark.asyncio
+class TestAuthServiceLogin:
+ async def test_login_returns_token_pair(
+ self, auth_service, login_request, active_user, mock_user_repo
+ ):
+ active_user.hashed_password = hash_password(login_request.password)
+ mock_user_repo.get_by_email.return_value = active_user
+
+ result = await auth_service.login(login_request)
+ assert result.access_token
+ assert result.refresh_token
+
+ async def test_login_wrong_password_raises(
+ self, auth_service, login_request, active_user, mock_user_repo
+ ):
+ active_user.hashed_password = hash_password("DifferentP@ss1")
+ mock_user_repo.get_by_email.return_value = active_user
+
+ with pytest.raises(InvalidCredentialsError):
+ await auth_service.login(login_request)
+
+ async def test_login_unknown_email_raises(
+ self, auth_service, login_request, mock_user_repo
+ ):
+ mock_user_repo.get_by_email.return_value = None
+ with pytest.raises(InvalidCredentialsError):
+ await auth_service.login(login_request)
+
+ async def test_login_inactive_user_raises(
+ self, auth_service, login_request, inactive_user, mock_user_repo
+ ):
+ inactive_user.hashed_password = hash_password(login_request.password)
+ mock_user_repo.get_by_email.return_value = inactive_user
+
+ with pytest.raises(UserInactiveError):
+ await auth_service.login(login_request)
+
+ async def test_login_persists_refresh_token(
+ self, auth_service, login_request, active_user, mock_user_repo, mock_token_repo
+ ):
+ active_user.hashed_password = hash_password(login_request.password)
+ mock_user_repo.get_by_email.return_value = active_user
+
+ await auth_service.login(login_request)
+ mock_token_repo.save.assert_awaited()
+
+
+@pytest.mark.asyncio
+class TestAuthServiceRefresh:
+ async def test_refresh_returns_new_access_token(
+ self,
+ auth_service,
+ active_user,
+ valid_refresh_token,
+ mock_user_repo,
+ mock_token_repo,
+ ):
+ real_refresh = create_refresh_token(active_user.id)
+ valid_refresh_token.token = real_refresh
+
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ mock_user_repo.get_by_id.return_value = active_user
+
+ result = await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+ assert result.access_token
+
+ async def test_refresh_revokes_old_token(
+ self,
+ auth_service,
+ active_user,
+ valid_refresh_token,
+ mock_user_repo,
+ mock_token_repo,
+ ):
+ real_refresh = create_refresh_token(active_user.id)
+ valid_refresh_token.token = real_refresh
+
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ mock_user_repo.get_by_id.return_value = active_user
+
+ await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+ assert valid_refresh_token.revoked is True
+
+ async def test_refresh_blacklists_old_token_in_redis(
+ self,
+ auth_service,
+ active_user,
+ valid_refresh_token,
+ mock_user_repo,
+ mock_token_repo,
+ mock_redis,
+ ):
+ real_refresh = create_refresh_token(active_user.id)
+ valid_refresh_token.token = real_refresh
+
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ mock_user_repo.get_by_id.return_value = active_user
+
+ await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+ mock_redis.setex.assert_awaited_once()
+ key = mock_redis.setex.call_args[0][0]
+ assert key.startswith("blacklist:")
+
+ async def test_refresh_unknown_token_raises(
+ self, auth_service, active_user, mock_token_repo
+ ):
+ real_refresh = create_refresh_token(active_user.id)
+ mock_token_repo.get_by_token.return_value = None
+
+ with pytest.raises(InvalidTokenError):
+ await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+
+ async def test_refresh_revoked_token_raises_and_revokes_all(
+ self,
+ auth_service,
+ active_user,
+ revoked_refresh_token,
+ mock_user_repo,
+ mock_token_repo,
+ ):
+ real_refresh = create_refresh_token(active_user.id)
+ revoked_refresh_token.token = real_refresh
+
+ mock_token_repo.get_by_token.return_value = revoked_refresh_token
+
+ with pytest.raises(InvalidTokenError):
+ await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+
+ mock_token_repo.revoke_all_for_user.assert_awaited_once_with(active_user.id)
+
+ async def test_refresh_inactive_user_raises(
+ self,
+ auth_service,
+ inactive_user,
+ valid_refresh_token,
+ mock_user_repo,
+ mock_token_repo,
+ ):
+ real_refresh = create_refresh_token(inactive_user.id)
+ valid_refresh_token.token = real_refresh
+
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ mock_user_repo.get_by_id.return_value = inactive_user
+
+ with pytest.raises(UserInactiveError):
+ await auth_service.refresh(RefreshRequest(refresh_token=real_refresh))
+
+ async def test_refresh_with_garbage_jwt_raises(self, auth_service):
+ with pytest.raises(InvalidTokenError):
+ await auth_service.refresh(RefreshRequest(refresh_token="garbage"))
+
+
+@pytest.mark.asyncio
+class TestAuthServiceLogout:
+ async def test_logout_revokes_stored_token(
+ self, auth_service, valid_refresh_token, mock_token_repo
+ ):
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ await auth_service.logout(valid_refresh_token.token)
+ assert valid_refresh_token.revoked is True
+
+ async def test_logout_blacklists_token(
+ self, auth_service, valid_refresh_token, mock_token_repo, mock_redis
+ ):
+ mock_token_repo.get_by_token.return_value = valid_refresh_token
+ await auth_service.logout(valid_refresh_token.token)
+ mock_redis.setex.assert_awaited_once()
+
+ async def test_logout_unknown_token_does_not_raise(
+ self, auth_service, mock_token_repo, mock_redis
+ ):
+ mock_token_repo.get_by_token.return_value = None
+ await auth_service.logout("unknown-token") # should complete silently
+ mock_redis.setex.assert_awaited_once()
+
+ async def test_logout_all_delegates_to_repo(
+ self, auth_service, active_user, mock_token_repo
+ ):
+ await auth_service.logout_all(active_user.id)
+ mock_token_repo.revoke_all_for_user.assert_awaited_once_with(active_user.id)
\ No newline at end of file
diff --git a/docs/API.md b/docs/API.md
deleted file mode 100644
index cd9b299..0000000
--- a/docs/API.md
+++ /dev/null
@@ -1,749 +0,0 @@
-# Структура и Спецификация API
-
-
-
->[!IMPORTANT]
-> Данная спецификация является единым источником истины (SSOT) для взаимодействия с API платформы STREMO. Документ разбит по микросервисам. Для каждой ручки приведены полные примеры JSON-ответов для всех возможных статусов (успешных и ошибок).
-
->[!NOTE]
-> **Базовый URL:** `https://api.stremo.com/v1`
-> Все закрытые эндпоинты требуют заголовок: `Authorization: Bearer `
-
----
-
-## **Auth-service**
-
-> Данный сервис отвечает за безопасность платформы. Он управляет регистрацией, выдачей и ротацией JWT-токенов, сессиями пользователей, а также интеграциями со сторонними провайдерами (**OAuth 2.0**) и двухфакторной аутентификацией (**Mobile ID**).
-
-#### *Классическая регистрация* `POST /auth/register`
-
-Создает новую учетную запись пользователя.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `email` | `string` | Да | Контактный email. Формат: валидный email (RFC 5322). Макс. 255 символов. |
-| `username` | `string` | Да | Уникальный никнейм. Только a-z, 0-9 и `_`. Длина: от 3 до 25 символов. |
-| `password` | `string` | Да | Мин. 8 символов, обязательно 1 заглавная буква, 1 строчная, 1 цифра, 1 спецсимвол. |
-
-Примеры ответов
-
->[!TIP]
-> **201 CREATED (OK)**
-> ```json
-> {
-> "id": "usr-550e8400-e29b-41d4-a716-446655440000",
-> "message": "User successfully registered. Please check your email for verification."
-> }
-> ```
-
->[!CAUTION]
-> **400 Bad Request**
-> ```json
-> {
-> "error": {
-> "code": "VALIDATION_FAILED",
-> "message": "Введенные данные не соответствуют требованиям",
-> "details": [
-> {
-> "field": "password",
-> "issue": "Пароль должен содержать как минимум один спецсимвол"
-> }
-> ],
-> "request_id": "req-99f5a-4b12"
-> }
-> }
-> ```
->
-> **409 Conflict**
-> ```json
-> {
-> "error": {
-> "code": "USER_ALREADY_EXISTS",
-> "message": "Пользователь с такими данными уже существует",
-> "details": [
-> {
-> "field": "username",
-> "issue": "Никнейм streamer_pro уже занят"
-> }
-> ],
-> "request_id": "req-99f5b-5c23"
-> }
-> }
-> ```
-
-#### *Авторизация* `POST /auth/login`
-
-Проверяет учетные данные и возвращает пару токенов для доступа к API.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание |
-| :--- | :--- | :--- | :--- |
-| `email` | `string` | Да | Email пользователя. |
-| `password` | `string` | Да | Пароль в открытом виде. |
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI...", // Короткоживущий токен для запросов
-> "refresh_token": "def50200508a8a4f8d5...", // Долгоживущий токен для обновления сессии
-> "expires_in": 900, // Время жизни access_token в секундах (15 минут)
-> "token_type": "Bearer"
-> }
-> ```
-
->[!CAUTION]
-> **401 Unauthorized**
-> ```json
-> {
-> "error": {
-> "code": "INVALID_CREDENTIALS",
-> "message": "Неверный email или пароль",
-> "request_id": "req-11a2b-3c44"
-> }
-> }
-> ```
->
-> **403 Forbidden**
-> ```json
-> {
-> "error": {
-> "code": "ACCOUNT_BANNED",
-> "message": "Ваш аккаунт был перманентно заблокирован",
-> "request_id": "req-11a2b-3c45"
-> }
-> }
-> ```
->
-> **429 Too Many Requests**
-> ```json
-> {
-> "error": {
-> "code": "RATE_LIMIT_EXCEEDED",
-> "message": "Слишком много неудачных попыток входа. Попробуйте через 15 минут.",
-> "request_id": "req-11a2b-3c46"
-> }
-> }
-> ```
-
-#### *Инициализация Mobile ID (Беспарольный вход / 2FA)* `POST /auth/mobile-id/init`
-
-Отправляет Push-уведомление на телефон пользователя через провайдера связи.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `phone_number` | `string` | Да | Номер телефона в формате E.164 (например, `+12345678900`). |
-
-Примеры ответов
-
->[!TIP]
-> **202 Accepted**
-> ```json
-> {
-> "session_id": "mob-999888", // Сохраните этот ID для проверки статуса авторизации
-> "control_code": "4512", // Покажите этот код пользователю на экране!
-> "expires_in": 120 // Сессия сгорит через 120 секунд
-> }
-> ```
-
->[!CAUTION]
-> **404 Not Found**
-> ```json
-> {
-> "error": {
-> "code": "PHONE_NOT_REGISTERED",
-> "message": "Данный номер телефона не привязан ни к одному аккаунту",
-> "request_id": "req-22b3c-4d55"
-> }
-> }
-> ```
-
----
-
-## **User-profile-service**
-
-> Микросервис управляет публичной и приватной информацией о пользователях.
-
-#### *Получение профиля текущего пользователя* `GET /users/me`
-
-Возвращает полную информацию об авторизованном пользователе. Запрос требует JWT токен.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "id": "usr-123",
-> "username": "streamer_pro",
-> "email": "user@example.com", // Возвращается только владельцу
-> "bio": "Professional CS2 player",
-> "avatar_url": "https://cdn.stremo.com/avatars/usr-123.png",
-> "followers_count": 1500,
-> "is_verified": true // Галочка верификации канала
-> }
-> ```
-
->[!CAUTION]
-> **401 Unauthorized**
-> ```json
-> {
-> "error": {
-> "code": "UNAUTHORIZED",
-> "message": "Токен доступа не передан или истек",
-> "request_id": "req-33c4d-5e66"
-> }
-> }
-> ```
-
-#### *Загрузка аватара* `POST /users/me/avatar`
-
-Загружает новое изображение профиля в S3 хранилище. `Content-Type: multipart/form-data`.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "avatar_url": "https://cdn.stremo.com/avatars/usr-123_new.png",
-> "message": "Аватар успешно обновлен"
-> }
-> ```
-
->[!CAUTION]
-> **400 Bad Request**
-> ```json
-> {
-> "error": {
-> "code": "UNSUPPORTED_FILE_TYPE",
-> "message": "Разрешены только форматы JPEG, PNG и WebP",
-> "request_id": "req-44d5e-6f77"
-> }
-> }
-> ```
->
-> **413 Payload Too Large**
-> ```json
-> {
-> "error": {
-> "code": "FILE_TOO_LARGE",
-> "message": "Размер файла превышает лимит в 5 МБ",
-> "request_id": "req-44d5e-6f78"
-> }
-> }
-> ```
-
----
-
-## **Billing-service**
-
-> Микросервис обрабатывает транзакции с балансом (донат Bits, подписки).
-
-#### *Отправка доната (Bits) на стрим* `POST /billing/donate`
-
-Списывает внутреннюю валюту со счета зрителя и переводит стримеру.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `channel_id` | `string` | Да | UUID стримера, которому предназначается донат. |
-| `amount_bits` | `integer` | Да | Количество отправляемой валюты. Минимум: 10, Максимум: 500,000. |
-| `message` | `string` | Нет | Сообщение для стримера. Макс. 255 символов. |
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "transaction_id": "txn-abc123def456",
-> "remaining_balance": 4500, // Новый баланс зрителя после списания
-> "message": "Донат успешно отправлен"
-> }
-> ```
-
->[!CAUTION]
-> **400 Bad Request**
-> ```json
-> {
-> "error": {
-> "code": "AMOUNT_TOO_LOW",
-> "message": "Минимальная сумма доната составляет 10 Bits",
-> "request_id": "req-55e6f-7a88"
-> }
-> }
-> ```
->
-> **402 Payment Required**
-> ```json
-> {
-> "error": {
-> "code": "INSUFFICIENT_FUNDS",
-> "message": "На вашем балансе недостаточно Bits для совершения операции",
-> "details": [
-> {
-> "field": "amount_bits",
-> "issue": "Требуется 500, доступно 150"
-> }
-> ],
-> "request_id": "req-55e6f-7a89"
-> }
-> }
-> ```
->
-> **404 Not Found**
-> ```json
-> {
-> "error": {
-> "code": "CHANNEL_NOT_FOUND",
-> "message": "Стример с указанным ID не найден",
-> "request_id": "req-55e6f-7a90"
-> }
-> }
-> ```
-
----
-
-## **Stream-meta-service**
-
-> Микросервис управляет метаданными эфира, категориями и каталогом трансляций.
-
-#### *Изменение метаданных трансляции* `PUT /streams/{stream_id}/meta`
-
-Обновляет карточку стрима. Рассылает WebSocket событие всем текущим зрителям.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `title` | `string` | Да | Название стрима (от 1 до 140 символов). |
-| `category_id` | `string` | Да | ID игры (например, `cs2`). |
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "stream_id": "str-987",
-> "title": "Road to Global Elite | CS2",
-> "category_id": "cs2",
-> "updated_at": "2026-05-20T10:05:00Z"
-> }
-> ```
-
->[!CAUTION]
-> **400 Bad Request**
-> ```json
-> {
-> "error": {
-> "code": "CATEGORY_NOT_FOUND",
-> "message": "Указанная категория игры не существует в базе",
-> "request_id": "req-66f7a-8b11"
-> }
-> }
-> ```
->
-> **403 Forbidden**
-> ```json
-> {
-> "error": {
-> "code": "NOT_STREAM_OWNER",
-> "message": "У вас нет прав на редактирование этой трансляции",
-> "request_id": "req-66f7a-8b12"
-> }
-> }
-> ```
-
-#### *Список активных трансляций (Каталог)* `GET /streams/live`
-
-Возвращает список текущих эфиров. Поддерживает курсорную пагинацию.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "data": [
-> {
-> "stream_id": "str-987",
-> "user": {
-> "id": "usr-123",
-> "username": "streamer_pro",
-> "avatar_url": "https://cdn.stremo.com/avatars/usr-123.png"
-> },
-> "title": "Grand Final!",
-> "category": {"id": "cs2", "name": "Counter-Strike 2"},
-> "viewers": 15405, // Текущий онлайн (CCV) обновляется раз в 15 секунд
-> "thumbnail_url": "https://cdn.stremo.com/thumbs/str-987.jpg", // Скриншот потока (обновляется раз в 5 минут)
-> "tags": ["esports", "ru"],
-> "is_mature": false
-> }
-> ],
-> "next_cursor": "YXNkcW..." // Использовать в следующем GET запросе для подгрузки при скролле вниз
-> }
-> ```
-
----
-
-## **Chat-service**
-
-> Микросервис доставляет текстовые сообщения сотням тысяч зрителей в реальном времени.
-
-#### *Получение истории сообщений* `GET /chat/{channel_id}/history`
-
-Запрашивается клиентом сразу после подключения к WS для инициализации окна чата.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "messages": [
-> {
-> "id": "msg-001",
-> "author": "viewer1",
-> "text": "Привет стример!",
-> "badges": ["subscriber", "vip"], // Значки пользователя
-> "timestamp": "2026-05-20T10:00:00Z"
-> },
-> {
-> "id": "msg-002",
-> "author": "viewer2",
-> "text": "GG WP",
-> "badges": [],
-> "timestamp": "2026-05-20T10:00:05Z"
-> }
-> ]
-> }
-> ```
-
->[!CAUTION]
-> **404 Not Found**
-> ```json
-> {
-> "error": {
-> "code": "CHANNEL_NOT_FOUND",
-> "message": "Чат для данного канала не найден",
-> "request_id": "req-77a8b-9c22"
-> }
-> }
-> ```
-
----
-
-## **Moderation-service**
-
-> Обрабатывает выдачу банов, таймаутов и автоматическую проверку сообщений на спам.
-
-#### *Принудительный бан в чате* `POST /moderation/channels/{channel_id}/ban`
-
-Блокирует пользователя на канале и выкидывает его из WebSocket соединения.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `user_id` | `string` | Да | ID пользователя-нарушителя. |
-| `reason` | `string` | Нет | Причина бана (остается в логах для апелляции). |
-| `delete_recent` | `boolean` | Нет | Если `true`, удаляет все сообщения пользователя за последние 10 минут. По умолчанию `true`. |
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "status": "banned",
-> "target_user_id": "usr-badguy",
-> "moderator_id": "usr-mod123",
-> "message": "Пользователь забанен, его недавние сообщения удалены"
-> }
-> ```
-
->[!CAUTION]
-> **403 Forbidden**
-> ```json
-> {
-> "error": {
-> "code": "MISSING_PERMISSIONS",
-> "message": "У вас нет прав модератора для выполнения этого действия",
-> "request_id": "req-88b9c-0d33"
-> }
-> }
-> ```
->
-> **409 Conflict**
-> ```json
-> {
-> "error": {
-> "code": "ALREADY_BANNED",
-> "message": "Этот пользователь уже забанен на данном канале",
-> "request_id": "req-88b9c-0d34"
-> }
-> }
-> ```
-
----
-
-## **Analytics-service**
-
-> Считает статистику в реальном времени (CCV) и агрегирует метрики.
-
-#### *Статистика стрима* `GET /analytics/streams/{stream_id}/summary`
-
-Возвращает бизнес-показатели завершенной или текущей трансляции для стримера.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "stream_id": "str-987",
-> "peak_viewers": 16500, // Пиковый онлайн
-> "average_viewers": 12400, // Средний онлайн
-> "new_followers": 340, // Сколько новых подписчиков пришло
-> "revenue_bits": 45000, // Заработано Bits
-> "revenue_fiat": 120.50 // Заработано фиата (с платных подписок)
-> }
-> ```
-
->[!CAUTION]
-> **403 Forbidden**
-> ```json
-> {
-> "error": {
-> "code": "ACCESS_DENIED",
-> "message": "Вы можете просматривать аналитику только своих трансляций",
-> "request_id": "req-99c0d-1e44"
-> }
-> }
-> ```
-
----
-
-## **Notification-service**
-
-> Управляет колокольчиком (In-App уведомления).
-
-#### *Список уведомлений* `GET /notifications`
-
-Возвращает список непрочитанных уведомлений.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "data": [
-> {
-> "id": "notif-001",
-> "type": "stream_started", // Тип уведомления
-> "title": "streamer_pro начал трансляцию!",
-> "body": "Заходи смотреть Road to Global Elite",
-> "action_url": "https://stremo.com/streamer_pro",
-> "created_at": "2026-05-20T09:00:00Z",
-> "is_read": false
-> }
-> ],
-> "unread_count": 1
-> }
-> ```
-
->[!CAUTION]
-> **401 Unauthorized**
-> ```json
-> {
-> "error": {
-> "code": "UNAUTHORIZED",
-> "message": "Требуется авторизация для просмотра уведомлений",
-> "request_id": "req-00d1e-2f55"
-> }
-> }
-> ```
-
----
-
-## **Vod-manager-service**
-
-> Микросервис управляет доступом к сохраненным записям прошлых трансляций (Video on Demand) и клипам.
-
-#### *Список записей канала* `GET /vods/channel/{channel_id}`
-
-Возвращает список сохраненных видео выбранного канала. Поддерживает пагинацию.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "data": [
-> {
-> "vod_id": "vod-8877",
-> "title": "Grand Final CS2",
-> "duration_seconds": 14400, // Длительность: 4 часа
-> "views": 5400,
-> "thumbnail_url": "https://cdn.stremo.com/vod-thumbs/vod-8877.jpg",
-> "created_at": "2026-05-19T18:00:00Z"
-> }
-> ],
-> "next_cursor": "QWVyZ..."
-> }
-> ```
-
->[!CAUTION]
-> **404 Not Found**
-> ```json
-> {
-> "error": {
-> "code": "CHANNEL_NOT_FOUND",
-> "message": "Запрашиваемый канал не существует",
-> "request_id": "req-11a2b-3c88"
-> }
-> }
-> ```
-
----
-
-## **Ingest-service**
-
-> Микросервис отвечает за прием видео-потока по протоколам RTMP/SRT. Включает API для управления настройками потока.
-
-#### *Получение ключа трансляции* `GET /ingest/me/key`
-
-Возвращает секретный Stream Key для настройки OBS.
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "stream_url": "rtmp://ingest.stremo.com/live",
-> "stream_key": "live_123456_abcdef9876543210" // Секретный ключ!
-> }
-> ```
-
->[!CAUTION]
-> **401 Unauthorized**
-> ```json
-> {
-> "error": {
-> "code": "UNAUTHORIZED",
-> "message": "Для получения ключа трансляции необходимо авторизоваться",
-> "request_id": "req-22b3c-4d99"
-> }
-> }
-> ```
-
----
-
-## **Transcoder-service (Внутреннее API)**
-
-> Микросервис принимает команды от Ingest Service и отвечает за запуск изолированных воркеров FFmpeg. Недоступен из внешней сети.
-
-#### *Вебхук готовности HLS-плейлиста* `POST /internal/v1/transcoder/status`
-
-Отправляется воркером, когда видеопоток успешно сконвертирован в различные качества (1080p, 720p) и HLS (.m3u8) чанки загружены в S3.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание и Валидация |
-| :--- | :--- | :--- | :--- |
-| `stream_id` | `string` | Да | ID текущей трансляции. |
-| `status` | `string` | Да | Статус транскодинга: `processing`, `completed`, `error`. |
-| `master_playlist_url` | `string` | Нет | URL к HLS master.m3u8 плейлисту. |
-
-Примеры ответов
-
->[!TIP]
-> **200 OK**
-> ```json
-> {
-> "message": "Status updated successfully",
-> "ack": true
-> }
-> ```
-
->[!CAUTION]
-> **422 Unprocessable Entity**
-> ```json
-> {
-> "error": {
-> "code": "INVALID_PAYLOAD",
-> "message": "Отсутствуют обязательные параметры",
-> "request_id": "req-internal-abc-123"
-> }
-> }
-> ```
-
----
-
-## **Smtp-service (Внутреннее API)**
-
-> Микросервис изолирует всю логику по отправке транзакционных писем. Вызывается по сети кластера (cluster.local) другими сервисами (например, Auth Service).
-
-#### *Отправка письма по шаблону* `POST /internal/v1/mail/send`
-
-Добавляет письмо в очередь на отправку.
-
-**Request Body**
-
-| Поле | Тип | Обяз. | Описание |
-| :--- | :--- | :--- | :--- |
-| `to` | `string` | Да | Email получателя. |
-| `template_id` | `string` | Да | ID HTML-шаблона. |
-| `variables` | `object` | Да | JSON-объект с переменными для подстановки в шаблон. |
-
-Примеры ответов
-
->[!TIP]
-> **202 Accepted**
-> ```json
-> {
-> "status": "queued",
-> "job_id": "job-555123", // Внутренний ID задачи в брокере (Kafka/Redis)
-> "message": "Письмо добавлено в очередь на отправку"
-> }
-> ```
-
->[!CAUTION]
-> **400 Bad Request**
-> ```json
-> {
-> "error": {
-> "code": "TEMPLATE_ERROR",
-> "message": "Не переданы обязательные переменные шаблона: username",
-> "request_id": "req-internal-xyz-999"
-> }
-> }
-> ```
->
-> **500 Internal Server Error**
-> ```json
-> {
-> "error": {
-> "code": "SMTP_GATEWAY_ERROR",
-> "message": "Не удалось подключиться к внешнему провайдеру (SendGrid/Mailgun)",
-> "request_id": "req-internal-err-500"
-> }
-> }
-> ```
-
-
----
-*by finnik*
diff --git a/docs/Archicture.md b/docs/Archicture.md
deleted file mode 100644
index c14fc39..0000000
--- a/docs/Archicture.md
+++ /dev/null
@@ -1,376 +0,0 @@
-# Архитектура Платформы STREMO
-
-
-
->[!IMPORTANT]
-> Данная спецификация является единым источником истины (SSOT) для высокоуровневой и низкоуровневой архитектуры стриминговой платформы STREMO. Документ описывает глобальную топологию, паттерны проектирования, потоки данных и внутреннее устройство каждого ключевого микросервиса.
-
->[!NOTE]
-> Основной технологический стек бэкенда: **C++20**. Выбор обусловлен необходимостью детерминированного управления памятью, отсутствием пауз сборщика мусора (GC) при обработке видеопотоков в реальном времени и максимальной производительностью сетевого слоя (Boost.Asio / gRPC).
-
----
-
-## **1. Глобальная Топология Платформы**
-
-Система разделена на независимые домены (Video, Core, Interactive). Весь внешний трафик проходит через Edge-балансировщики, фильтруется и перенаправляется на внутренние C++ микросервисы.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 160, "nodeSpacing": 15}}}%%
-flowchart LR
- classDef default fill:#fff,stroke:#000,stroke-width:2px,color:#000
- classDef dashedRed fill:#fff,stroke:#d63031,stroke-width:2px,stroke-dasharray: 6 6,color:#d63031,rx:15,ry:15
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Streamers("Streamers\n(OBS)"):::default
- Users("Viewers\n(Web/App)"):::default
-
- Streamers -->|"RTMP: TCP 1935"| TCPProxy("tcp_proxy\n(HAProxy)"):::dashedRed
- TCPProxy -->|"Raw Stream"| Ingest("ingest_service\n(C++)"):::solidBlack
- Ingest -->|"IPC / Shared Mem"| Transcoder("transcoder_service\n(FFmpeg)"):::solidBlack
- Transcoder -->|"PUT /hls/*.ts"| S3("s3_storage\n(Multi-AZ)"):::storage
-
- Users -->|"GET /master.m3u8"| CDN("cloud_cdn\n(Edge Cache)"):::dashedBlack
- CDN -.->|"Cache Miss"| S3
-
- Users -->|"REST API (HTTPS)"| Nginx("nginx\n(Ingress, WAF)"):::dashedRed
- Nginx -->|"Rate Limited"| BFF("BFF_service\n(API Gateway)"):::dashedBlack
-
- BFF -->|"gRPC: VerifyToken"| Auth("auth_service"):::solidBlack
- BFF -->|"gRPC: GetProfile"| User("user_service"):::solidBlack
- BFF -->|"gRPC: GetMeta"| StreamMeta("stream_meta_service"):::solidBlack
- BFF -->|"gRPC: GetClips"| VOD("vod_service"):::solidBlack
- BFF -->|"gRPC: Checkout"| Billing("billing_service"):::solidBlack
-
- Auth & User & StreamMeta & VOD & Billing --> DB("postgresql_cluster\n(Sharded by: channel_id)"):::storage
-
- Users -.->|"WSS: /ws/events"| Nginx
- Nginx -->|"Upgrade WSS"| Chat("chat_service\n(WebSockets)"):::solidBlack
-
- Chat -->|"PUBLISH chat:{id}"| Redis("redis_cluster\n(Pub/Sub & Hot State)"):::storage
- Chat -.->|"gRPC: Check Spam"| Mod("mod_service\n(ML Filters)"):::solidBlack
-
- Ingest -.->|"gRPC: Report CCV"| Analytics("analytics_service\n(Aggregator)"):::solidBlack
- Analytics -->|"INSERT Batch (5s)"| Clickhouse("clickhouse\n(ReplicatedMergeTree)"):::storage
-
- Ingest -.->|"Verify Stream Key"| StreamMeta
- Ingest -.->|"stream.started"| Kafka("kafka_bus\n(Partitions: 32)"):::storage
- Billing -.->|"payment.received"| Kafka
- Mod -.->|"user.banned"| Kafka
-
- Kafka -.->|"Consume Events"| Notify("notify_service\n(WebPush/SSE)"):::solidBlack
- Notify -.->|"Push Alert"| Users
-```
-
----
-
-## **2. Детальная Архитектура Доменов и Микросервисов**
-
-Каждый микросервис спроектирован с учетом изоляции отказов (Fault Isolation) и возможности независимого горизонтального масштабирования (Horizontal Pod Autoscaling).
-
-### **2.1. Домен Стриминга: Видео Пайплайн (Ingest & Transcoder)**
-
-Самый ресурсоемкий узел платформы. Цель пайплайна — получить сырой видеопоток, конвертировать его в различные разрешения (Adaptive Bitrate) и доставить зрителям с минимальной задержкой.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 30}}}%%
-flowchart TD
- classDef dashedRed fill:#fff,stroke:#d63031,stroke-width:2px,stroke-dasharray: 6 6,color:#d63031,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Input("TCP Payload (RTMP/SRT)"):::dashedRed --> Ingest("Ingest Service\n(Asio Event Loop)"):::solidBlack
-
- subgraph K8s Node: Compute Optimized
- Ingest -->|"Spawn Process"| Supervisor("Transcoder Supervisor\n(Subprocess Manager)"):::solidBlack
- Supervisor -->|"Pipe stdout/stdin"| FFmpeg1("FFmpeg Worker 1\n(1080p)"):::dashedRed
- Supervisor -->|"Pipe stdout/stdin"| FFmpeg2("FFmpeg Worker 2\n(720p)"):::dashedRed
-
- FFmpeg1 -->|"Write Segments"| Tmpfs("RAM Disk (/tmpfs)"):::storage
- FFmpeg2 -->|"Write Segments"| Tmpfs
- end
-
- Tmpfs -->|"S3 Multipart Upload"| S3("Object Storage"):::storage
- Ingest -.->|"gRPC Call"| StreamMeta("Stream Meta Service"):::solidBlack
-```
-
-**Как это работает:**
-1. **Ingest Service (Прием):** Терминирует RTMP соединение. Первым делом делает синхронный gRPC вызов в `stream-meta-service` для валидации Stream Key. Если ключ неверен, TCP соединение моментально сбрасывается.
-2. **Transcoder Supervisor:** Если ключ валиден, Ingest порождает (fork/exec) новые процессы FFmpeg. Связь между Ingest и FFmpeg идет через анонимные пайпы (IPC/Pipes), что исключает сетевые задержки внутри узла.
-3. **HLS & RAM Disk:** FFmpeg нарезает видео на чанки (2 секунды). Чтобы не изнашивать SSD сервера тысячами операций записи в секунду, чанки пишутся в оперативную память (RAM Disk / `tmpfs`).
-4. **Upload:** Фоновый воркер асинхронно выгружает готовые файлы из `tmpfs` в S3. По завершении загрузки манифеста вызывается webhook-уведомление внутреннего API платформы.
-
->[!CAUTION]
-> **Привязка к железу (Node Affinity)**
-> Поды Ingest и Transcoder разворачиваются строго на узлах Kubernetes с маркировкой `video-optimized`. Эти узлы имеют мощные процессоры и аппаратные энкодеры GPU (NVENC), проброшенные в контейнер через драйверы NVIDIA.
-
----
-
-### **2.2. Домен Интерактива: Чат и Модерация (Realtime Pub/Sub)**
-
-Домен чата должен выдерживать скачкообразные нагрузки (Thundering Herd) во время масштабных киберспортивных турниров (до 500,000 онлайна).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 120, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Viewer1("Viewer 1"):::dashedBlack -->|"WSS /ws/chat"| ChatPod1("Chat Service Pod 1"):::solidBlack
- Viewer2("Viewer 2"):::dashedBlack -->|"WSS /ws/chat"| ChatPod2("Chat Service Pod 2"):::solidBlack
-
- ChatPod1 -->|"Validate"| ModSvc("Moderation Svc\n(NLP Spam Filter)"):::solidBlack
-
- ChatPod1 -->|"PUBLISH chat:channel_id"| Redis("Redis Cluster\n(Pub/Sub)"):::storage
-
- Redis -->|"SUBSCRIBE chat:channel_id"| ChatPod2
- Redis -->|"SUBSCRIBE chat:channel_id"| ChatPod1
-
- ChatPod2 -->|"Broadcast WS"| Viewer2
-
- ChatPod1 -.->|"Batch INSERT (500 ms)"| Citus("Postgres\n(Citus Shard)"):::storage
-```
-
-**Как это работает:**
-1. **WebSocket Балансировка:** Балансировщик нагрузки (Ingress) распределяет WSS-соединения зрителей случайным образом по сотням экземпляров (подов) `chat-service`.
-2. **Синхронизация через Redis:** Когда зритель отправляет сообщение на `ChatPod1`, этот под обязан доставить его всем остальным зрителей в этой же комнате. Под публикует сообщение в Redis-канал `chat:`.
-3. **Рассылка (Fan-Out):** Все остальные поды (например, `ChatPod2`), которые держат соединения зрителей из этого же канала, подписаны на этот Redis-канал. Они получают событие и пересылают его в открытые WebSocket-сокеты своих клиентов.
-4. **Сжатие базы данных:** Чтобы не перегрузить базу одиночными инсертами, каждый под накапливает сообщения в кольцевом буфере памяти и сбрасывает их в PostgreSQL одним `COPY` или многострочным `INSERT` запросом раз в полсекунды.
-
->[!TIP]
-> **Автоматический Slow Mode**
-> Если Redis фиксирует превышение лимита (например, больше 100 сообщений в секунду для одного канала), `chat-service` автоматически включает "Slow Mode", заставляя клиентов выжидать паузу перед отправкой, чтобы предотвратить деградацию UI на фронтенде.
-
----
-
-### **2.3. Домен Биллинга: Гарантия согласованности (Transactional Outbox)**
-
-Биллинг работает с реальными деньгами и внутренней валютой (Bits). Ключевая проблема здесь — двойная запись (Dual Write): необходимость изменить баланс в базе данных и гарантированно отправить уведомление в шину событий (Kafka).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- API("REST API (Donate)"):::dashedBlack --> Billing("Billing Service\n(Transaction Manager)"):::solidBlack
-
- subgraph Transaction Boundary
- Billing -->|"1. BEGIN"| DB("PostgreSQL"):::storage
- Billing -->|"2. UPDATE users SET bits = bits - 100"| DB
- Billing -->|"3. INSERT INTO outbox (event_data)"| DB
- Billing -->|"4. COMMIT"| DB
- end
-
- DB -.->|"Logical Replication (WAL)"| Debezium("Debezium Relay\n(CDC)"):::solidBlack
- Debezium -->|"5. Produce"| Kafka("Kafka Broker\n(Topic: stream.alerts)"):::storage
-
- Kafka -->|"6. Consume"| Notify("Notify Service"):::solidBlack
- Notify -->|"7. Send Alert"| Streamer("Streamer Screen"):::dashedBlack
-```
-
-**Как это работает:**
-1. **ACID Транзакция:** Биллинг открывает транзакцию в PostgreSQL. Он списывает деньги со счета и *в этой же транзакции* записывает факт совершения платежа (событие для алерта) в системную таблицу `outbox`.
-2. **Гарантия целостности:** Если транзакция откатится (сбой сети, нехватка средств), событие не сохранится в `outbox`. Если зафиксируется — событие гарантированно в базе.
-3. **CDC (Change Data Capture):** Процесс Debezium непрерывно читает журнал упреждающей записи (WAL) PostgreSQL. Как только в таблице `outbox` появляется новая строка, Debezium конвертирует ее в событие Kafka.
-4. **Семантика At-Least-Once:** Даже если `billing-service` упадет сразу после `COMMIT`, система CDC все равно отправит событие в Kafka, гарантируя, что алерт доната обязательно появится на экране стримера.
-
----
-
-## **3. Архитектура Остальных Микросервисов**
-
-Помимо трех основных высоконагруженных доменов, описанных выше, платформа состоит из набора специализированных микросервисов. Ниже представлены схемы их внутреннего устройства.
-
-### **3.1. Auth Service (Сервис Аутентификации)**
-
-Отвечает за генерацию токенов, хеширование паролей и интеграцию с провайдерами связи для 2FA.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- BFF("API Gateway"):::dashedBlack -->|"REST: /login"| Auth("Auth Service\n(Argon2 Hashing)"):::solidBlack
-
- Auth -->|"Validate Credentials"| DB("PostgreSQL\n(Users Table)"):::storage
- Auth -->|"Generate JWT"| Auth
-
- Auth -->|"Set Rate Limit"| Redis("Redis\n(Token Bucket)"):::storage
-
- BFF -->|"REST: /mobile-id"| Auth
- Auth -->|"gRPC: Send SMS/Push"| SmtpSvc("SMTP / Telecom Provider"):::solidBlack
- Auth -->|"Store 2FA Session"| Redis
-```
-
-### **3.2. User Profile Service (Сервис Профилей)**
-
-Обрабатывает запросы на получение публичной информации о каналах и управление подписками (фолловерами).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- BFF("API Gateway"):::dashedBlack -->|"GET /users/me"| UserSvc("User Profile Svc"):::solidBlack
-
- UserSvc -->|"Check Cache"| Redis("Redis Cluster"):::storage
- Redis -.->|"Cache Miss"| DB("PostgreSQL\n(Global)"):::storage
- DB -.->|"Update Cache"| Redis
-
- BFF -->|"POST /avatar"| UserSvc
- UserSvc -->|"Resize & Compress"| UserSvc
- UserSvc -->|"Multipart Upload"| S3("S3 Storage"):::storage
-```
-
-### **3.3. Stream Meta Service (Сервис Метаданных)**
-
-Хранит каталог стримов, названия, категории и обеспечивает быстрый поиск (Keyset Pagination) для главной страницы.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- BFF("API Gateway"):::dashedBlack -->|"GET /streams/live"| MetaSvc("Stream Meta Svc"):::solidBlack
- IngestSvc("Ingest Service"):::dashedBlack -->|"gRPC: Verify Key"| MetaSvc
-
- MetaSvc -->|"Query Directory"| DB("PostgreSQL\n(Sharded)"):::storage
- MetaSvc -->|"Get Live CCV"| Redis("Redis\n(Live Viewers Cache)"):::storage
-
- BFF -->|"PUT /meta"| MetaSvc
- MetaSvc -->|"Update DB"| DB
- MetaSvc -->|"Event: MetaUpdated"| Kafka("Kafka Bus"):::storage
-```
-
-### **3.4. Moderation Service (Сервис Модерации)**
-
-Работает в связке с чатом. Использует NLP (Natural Language Processing) модели для автоматической фильтрации спама.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- ChatSvc("Chat Service"):::dashedBlack -->|"gRPC: CheckSpam(text)"| ModSvc("Moderation Svc\n(C++ Orchestrator)"):::solidBlack
- BFF("API Gateway"):::dashedBlack -->|"POST /ban"| ModSvc
-
- ModSvc -->|"TensorRT Inference"| NLP("ML Model\n(Spam Classifier)"):::solidBlack
-
- ModSvc -->|"Publish 'Ban' Event"| Redis("Redis Pub/Sub\n(Disconnect WS)"):::storage
- ModSvc -->|"Log Action"| DB("PostgreSQL\n(Audit Log)"):::storage
-```
-
-### **3.5. Analytics Service (Сервис Аналитики)**
-
-Считает онлайн (CCV) и строит агрегированные графики для стримеров. Из-за высоких нагрузок на запись использует ClickHouse.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- IngestSvc("Ingest Service"):::dashedBlack -->|"gRPC: Report Ping (15s)"| Analytics("Analytics Svc\n(Aggregator)"):::solidBlack
-
- Analytics -->|"Update Live CCV"| Redis("Redis"):::storage
- Analytics -->|"Buffer Metrics in Memory"| Analytics
-
- Analytics -->|"Bulk INSERT (Every 5s)"| ClickHouse("ClickHouse\n(Columnar DB)"):::storage
-
- BFF("API Gateway"):::dashedBlack -->|"GET /summary"| Analytics
- Analytics -->|"Fast SELECT"| ClickHouse
-```
-
-### **3.6. Notification Service (Сервис Уведомлений)**
-
-Читает события из шины данных и превращает их в In-App уведомления или мобильные Push-сообщения.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Kafka("Kafka Bus\n(stream.started)"):::storage -->|"Consume"| Notify("Notification Svc"):::solidBlack
-
- Notify -->|"Find Followers"| DB("PostgreSQL"):::storage
- Notify -->|"Save In-App Alert"| DB
-
- Notify -->|"Send Payload"| FCM("Firebase / APNs\n(Mobile Push)"):::solidBlack
- Notify -->|"SSE / WebPush"| Users("Web Clients"):::dashedBlack
-```
-
-### **3.7. VOD Manager Service (Управление Записями)**
-
-Предоставляет API для доступа к прошлым стримам (VOD) и создания коротких клипов из длинных трансляций.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- BFF("API Gateway"):::dashedBlack -->|"GET /vods"| VOD("VOD Manager Svc"):::solidBlack
- Transcoder("Transcoder Svc"):::dashedBlack -->|"Webhook: VOD Ready"| VOD
-
- VOD -->|"Save Metadata"| DB("PostgreSQL"):::storage
-
- BFF -->|"POST /clips"| VOD
- VOD -->|"Schedule Clip Job"| Kafka("Kafka Bus"):::storage
-```
-
-### **3.8. SMTP Service (Внутренний Почтовый Шлюз)**
-
-Изолирует логику рендеринга HTML шаблонов и общения с внешними провайдерами электронной почты. Никакой другой сервис не имеет прямого доступа к интернету для отправки писем.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- CoreSvc("Any Core Service\n(Auth, Billing)"):::dashedBlack -->|"POST /internal/mail"| SMTP("SMTP Service\n(Template Engine)"):::solidBlack
-
- SMTP -->|"Enqueue Job"| Redis("Redis Queue"):::storage
-
- SMTP -->|"Process Queue"| Mailgun("External Provider\n(SendGrid / Mailgun)"):::dashedBlack
-```
-
----
-
-## **4. Хранение Данных и Шардирование**
-
-Платформа избегает единой точки отказа (SPOF) на уровне хранения данных.
-
-* **Global Database (PostgreSQL):** Содержит критически важные, но редко изменяемые данные. Это таблицы пользователей, паролей, ролей, балансов и истории транзакций. База работает в режиме Primary-Replica.
-* **Sharded Database (PostgreSQL + Citus Data):** Содержит огромные объемы данных: историю чата, VOD-клипы, статистику и просмотры. Данные горизонтально распределены по нескольким узлам кластера на основе ключа распределения (`channel_id` или `stream_id`). Запросы маршрутизируются на нужный шард прозрачно для приложения.
-* **ClickHouse (Аналитика):** База данных столбцового типа (Columnar). Используется сервисом `analytics-service` для агрегации метрик. Принимает широкие инсерты (CCV, показы рекламы) и позволяет строить графики для дашборда стримера за миллисекунды.
-
----
-
-## **5. Сводный Технологический Стек**
-
-| Слой | Технология | Бизнес-обоснование выбора |
-| :--- | :--- | :--- |
-| **Backend Core** | C++20 | Ручное управление ресурсами. Использование корутин (coroutines) и современных библиотек (Boost.Asio) для предсказуемой задержки и высокой пропускной способности. |
-| **Внутреннее RPC** | gRPC (Protobuf) | Строгая типизация контрактов между командами разработчиков. Бинарная сериализация снижает нагрузку на сеть в 10 раз по сравнению с REST/JSON. |
-| **API Gateway** | Go / Node.js | Паттерн BFF (Backend for Frontend). Терминирует внешние JWT токены, агрегирует данные с разных gRPC сервисов и отдает фронтенду удобный JSON. |
-| **Видео Ядро** | FFmpeg (C API) | Индустриальный стандарт транскодинга. Позволяет тонко управлять пресетами кодеков (x264/NVENC) для достижения оптимального качества ABR. |
-| **Кэш / PubSub** | Redis 7 (Cluster) | Хранение "горячих" данных (сессии, профили), реализация Token Bucket (Rate Limit) и шина обмена сообщениями для чата (Pub/Sub). |
-| **Брокер Событий** | Apache Kafka | Развязка микросервисов (Decoupling) и гарантия доставки (Event Sourcing). Выдерживает колоссальные объемы записей без деградации производительности. |
-| **Инфраструктура** | Kubernetes (K8s) | Оркестрация контейнеров, изоляция ресурсов, автоматический Horizontal Pod Autoscaling (HPA) воркеров в зависимости от нагрузки. |
diff --git a/docs/CI-CD.md b/docs/CI-CD.md
deleted file mode 100644
index eabcc2b..0000000
--- a/docs/CI-CD.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# Непрерывная Интеграция и Качество Кода (CI Pipeline)
-
->[!IMPORTANT]
-> Данный документ детализирует процессы, происходящие с кодом до его попадания в среду развертывания (которая описана в `Deploy.md`). Строгий CI-пайплайн гарантирует, что в Production попадет только проверенный, безопасный и оптимизированный код.
-
----
-
-## **1. Архитектура CI Пайплайна (GitLab CI / GitHub Actions)**
-
-Пайплайн запускается автоматически при создании Pull Request (Merge Request) или коммите в ветку `main`. Для C++ микросервисов процесс сборки и тестирования разделен на жесткие этапы (Stages), каждый из которых блокирует дальнейшее продвижение при возникновении ошибки.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Trigger("Commit to 'main'\nor PR Created"):::dashedBlack
-
- subgraph Stage 1: Build & Lint
- direction LR
- ClangTidy("Clang-Tidy\n(Static Analysis)"):::solidBlack
- CMake("CMake Build\n(GCC / Clang)"):::solidBlack
- end
-
- subgraph Stage 2: Testing
- direction LR
- GTest("Unit Tests\n(GoogleTest)"):::solidBlack
- Valgrind("Valgrind\n(Memory Leaks Check)"):::storage
- end
-
- subgraph Stage 3: Security & SAST
- direction LR
- Sonar("SonarQube\n(Code Quality)"):::solidBlack
- Trivy("Trivy\n(Vulnerability Scan)"):::storage
- end
-
- subgraph Stage 4: Package & Release
- direction LR
- Docker("Docker Build\n(Multi-stage)"):::solidBlack
- Push("Push to Registry\n(ECR / Harbor)"):::storage
- end
-
- Trigger --> Stage1
- Stage1 -->|"If Passed"| Stage2
- Stage2 -->|"If Passed"| Stage3
- Stage3 -->|"If Passed"| Stage4
-```
-
----
-
-## **2. Детализация Этапов (Stages)**
-
-### **2.1. Сборка и Линтинг (Build & Lint)**
-
-Кодовая база C++20 требует строгих стандартов форматирования и проверки на этапе компиляции.
-
-* **Clang-Format:** Проверяет код на соответствие `.clang-format` файлу проекта (стандарт Google C++ Style Guide с локальными модификациями).
-* **Clang-Tidy:** Ловит потенциальные баги до компиляции (например, неинициализированные переменные, опасные приведения типов, нарушения `const-correctness`).
-* **Компиляция:** Сборка происходит через CMake. В пайплайне всегда включен флаг `-Werror` (считать Warnings ошибками), чтобы код с предупреждениями не мог быть слит в `main`.
-
-### **2.2. Тестирование и Анализ Памяти (Testing)**
-
-Отказоустойчивость сервисов реального времени (In-Memory буферы, WebSockets) критически зависит от работы с памятью.
-
-* **GoogleTest (GTest):** Запускает набор Unit-тестов для бизнес-логики.
-* **Valgrind / AddressSanitizer (ASan):** Бинарный файл запускается в специальном окружении для поиска утечек памяти (Memory Leaks), обращений к освобожденной памяти (Use-After-Free) и состояний гонки (Race Conditions).
-
->[!CAUTION]
-> **Блокировка пайплайна (Quality Gate)**
-> Покрытие Unit-тестами (Code Coverage) должно быть не ниже 80%. Если покрытие падает ниже этого порога, пайплайн завершается с ошибкой (`exit 1`), и слияние ветки (Merge Request) автоматически блокируется.
-
----
-
-## **3. Безопасность и SAST (Security Scanning)**
-
-STREMO обрабатывает платежные данные и трансляции, поэтому безопасность интегрирована прямо в процесс сборки (Shift-Left Security).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- SourceCode("Source Code"):::dashedBlack --> SAST("SonarQube\n(Code Vulnerabilities)"):::solidBlack
- DockerImage("Compiled Docker Image"):::dashedBlack --> Trivy("Trivy Scanner\n(OS Vulnerabilities)"):::storage
-
- SAST -->|"Quality Gate"| Result("Merge Request\nStatus"):::solidBlack
- Trivy -->|"Block High/Critical"| Result
-```
-
-* **SonarQube (SAST):** Анализирует исходный код на уязвимости (например, SQL Injection, Buffer Overflow) и технический долг (Code Smell).
-* **Trivy (Container Scanning):** После того как Docker-образ собран, Trivy сканирует его слои на предмет уязвимостей в системных библиотеках (CVE). Если найдены уязвимости уровня `CRITICAL` или `HIGH` в базовом образе, пуш в Registry блокируется.
-
----
-
-## **4. Оптимизация Docker-образов (Multi-Stage Build)**
-
-Поскольку C++ требует тяжелых компиляторов и библиотек (Boost, FFmpeg dev-headers), мы используем **Multi-Stage Builds** для создания финального образа, который пойдет в Production.
-
-1. **Stage 1 (Builder):** Использует тяжелый образ (`ubuntu` или `debian` с GCC/Clang, CMake, Conan/vcpkg). Здесь компилируется бинарный файл.
-2. **Stage 2 (Runtime):** Использует минималистичный образ `distroless/cc` (образ без shell, пакетного менеджера и лишних библиотек). В него копируется только скомпилированный бинарный файл и нужные `.so` файлы.
-
->[!TIP]
-> **Преимущества Distroless:**
-> Финальный образ микросервиса весит около 20-30 МБ вместо 1 ГБ+. Это кардинально ускоряет время запуска пода (Cold Start) в Kubernetes во время автомасштабирования (HPA) и сводит площадь атаки (Attack Surface) практически к нулю (внутри контейнера нет даже `bash`).
\ No newline at end of file
diff --git a/docs/DataBase.md b/docs/DataBase.md
deleted file mode 100644
index de98464..0000000
--- a/docs/DataBase.md
+++ /dev/null
@@ -1,169 +0,0 @@
-# Архитектура Баз Данных STREMO
-
-
-
->[!IMPORTANT]
-> База данных — самый критичный компонент платформы. STREMO использует подход гибридного хранения данных (Polyglot Persistence). Документ описывает разделение на Глобальный кластер (для транзакций) и Шардированный кластер (для тяжелых данных), а также механизмы репликации и отказоустойчивости.
-
----
-
-## **1. Глобальная Топология Данных**
-
-Чтобы избежать бутылочного горлышка (Bottleneck) при росте платформы, данные строго разделены на три независимых кластера в зависимости от паттерна их использования (Read-Heavy, Write-Heavy или Analytical).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 20}}}%%
-flowchart TD
- classDef cluster fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#334155,rx:10,ry:10,stroke-dasharray: 5 5
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- BFF("API Gateway / Microservices"):::solidBlack
-
- subgraph Cluster_Global ["1. Global Transactional DB (PostgreSQL)"]
- direction TB
- DB_Users("Users, Auth, Billing"):::storage
- end
-
- subgraph Cluster_Sharded ["2. Sharded Distributed DB (Citus / Postgres)"]
- direction TB
- DB_Streams("Chat History, VODs, Stream Meta"):::storage
- end
-
- subgraph Cluster_Analytics ["3. OLAP Analytics (ClickHouse)"]
- direction TB
- DB_Metrics("CCV, Ad Impressions, Traffic"):::storage
- end
-
- BFF -->|"ACID Transactions"| Cluster_Global
- BFF -->|"High Write Throughput"| Cluster_Sharded
- BFF -->|"Analytical Queries"| Cluster_Analytics
-```
-
----
-
-## **2. Глобальный Кластер (PostgreSQL)**
-
-Этот кластер хранит "золотые данные" платформы. Здесь происходят финансовые операции, хранятся профили, пароли и настройки.
-* **Паттерн:** Read-Heavy (часто читают профили, редко меняют).
-* **Требования:** Строгий ACID, гарантия отсутствия потерь при сбоях.
-
-### **2.1 Схема Репликации (High Availability)**
-
-Для обеспечения доступности (HA) используется асинхронная потоковая репликация (Streaming Replication) с автоматическим переключением (Failover) через Patroni или PgBouncer.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 40}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Router("PgBouncer / HAProxy\n(Connection Pooling)"):::solidBlack
-
- Master("PostgreSQL PRIMARY\n(Read / Write)"):::storage
- Replica1("PostgreSQL REPLICA 1\n(Read-Only)"):::storage
- Replica2("PostgreSQL REPLICA 2\n(Read-Only)"):::storage
-
- Router -->|"Writes (UPDATE/INSERT)"| Master
- Router -.->|"Reads (GET /users)"| Replica1
- Router -.->|"Reads (GET /users)"| Replica2
-
- Master ==>|"WAL Streaming (Async)"| Replica1
- Master ==>|"WAL Streaming (Async)"| Replica2
-```
-
-### **2.2 Основные Таблицы Глобального Кластера**
-
-* `users`: ID, email, хэш пароля, дата регистрации.
-* `profiles`: bio, avatar_url, настройки уведомлений.
-* `wallets`: баланс fiat, баланс Bits (строгие транзакционные блокировки `SELECT FOR UPDATE`).
-* `outbox_events`: системная таблица для паттерна Transactional Outbox (события для отправки в Kafka).
-* `followers`: связь Many-to-Many между зрителями и каналами.
-
----
-
-## **3. Шардированный Кластер (Citus Data)**
-
-Самая большая проблема стриминговой платформы — чат. Если 100,000 человек одновременно пишут в разные чаты, один мастер-сервер PostgreSQL не выдержит нагрузки на запись (Write-Heavy). Мы решаем это с помощью расширения **Citus**, которое превращает PostgreSQL в распределенную СУБД.
-
-### **3.1 Принцип Шардирования (Horizontal Scaling)**
-
-Данные разбиваются на части (шарды) и распределяются по разным физическим серверам (Worker Nodes).
-* **Ключ распределения (Distribution Key):** `channel_id`.
-* **Почему `channel_id`?** Это гарантирует, что вся история чата конкретного стримера, все его клипы и метаданные его стрима будут лежать на **одном физическом диске**. Это позволяет делать быстрые локальные `JOIN` запросы без пересылки данных по сети между нодами.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 15}}}%%
-flowchart TD
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- App("Chat Service / Stream Meta Svc"):::solidBlack
-
- Coordinator("Citus Coordinator Node\n(Query Router)"):::storage
-
- App -->|"INSERT INTO chat_messages\n(channel_id=1)"| Coordinator
- App -->|"INSERT INTO chat_messages\n(channel_id=9)"| Coordinator
- App -->|"INSERT INTO chat_messages\n(channel_id=4)"| Coordinator
-
- subgraph Citus Worker Nodes
- direction LR
- Worker1("Worker Node A\n(Shards for ch: 1, 2, 3)"):::storage
- Worker2("Worker Node B\n(Shards for ch: 4, 5, 6)"):::storage
- Worker3("Worker Node C\n(Shards for ch: 7, 8, 9)"):::storage
- end
-
- Coordinator ==>|"Routes ch=1"| Worker1
- Coordinator ==>|"Routes ch=4"| Worker2
- Coordinator ==>|"Routes ch=9"| Worker3
-```
-
-### **3.2 Основные Таблицы Шардированного Кластера**
-
-* `chat_messages`: ID сообщения, `channel_id` (Sharding Key), `user_id`, текст, timestamp.
-* `streams`: ID трансляции, `channel_id` (Sharding Key), title, category, status.
-* `vods`: сохраненные видео, привязаны к `channel_id`.
-* `moderation_actions`: баны и таймауты (распределены по `channel_id` для быстрого доступа во время стрима).
-
->[!TIP]
-> **Автоматическая балансировка**
-> При добавлении новых серверов в кластер, Citus автоматически в фоновом режиме (не прерывая работу платформы) перенесет часть шардов на новые сервера, размазывая нагрузку.
-
----
-
-## **4. ClickHouse (Слой Аналитики)**
-
-Аналитика требует совершенно другого подхода. Стример хочет видеть график "Сколько зрителей было на каждой минуте моего 10-часового стрима". PostgreSQL будет выполнять такой запрос очень долго, сканируя миллионы строк.
-**ClickHouse** решает это благодаря столбцовому хранению и векторизованным вычислениям.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Ingest("Ingest Svc"):::solidBlack -->|"Publishes"| Kafka("Kafka Bus\n(metrics_topic)"):::storage
-
- Kafka -->|"Consume Batch\n(100k events/sec)"| CH_Buffer("ClickHouse\nKafka Engine Table"):::storage
-
- CH_Buffer -->|"Materialized View\n(Aggregates by Minute)"| CH_Final("ClickHouse\nMergeTree (Fast Reads)"):::storage
-
- BFF("API Gateway"):::solidBlack -->|"GET /dashboard"| CH_Final
-```
-
-### **4.1 Почему ClickHouse?**
-1. **Огромная скорость вставки:** Способен "заглатывать" сотни тысяч метрик в секунду напрямую из Kafka (через встроенный Kafka Table Engine).
-2. **Агрегация на лету:** Данные о зрителях сбрасываются каждую секунду. ClickHouse (через Materialized Views) автоматически сжимает их, вычисляя средний и пиковый онлайн (CCV) за каждую минуту.
-3. **Экономия диска:** Столбцовые данные отлично сжимаются (в 3-5 раз лучше, чем в PostgreSQL).
-
----
-
-## **5. Механизмы Кэширования (Redis Cluster)**
-
-Базы данных не должны обрабатывать 100% трафика. Все частые запросы экранируются кластером Redis 7.
-
-* **Token Bucket:** Хранит лимиты Rate Limit (защита от DDoS).
-* **Pub/Sub:** Мгновенная рассылка сообщений чата между подами Kubernetes (данные не сохраняются в Redis, только маршрутизируются).
-* **Сессии и JWT:** Список отозванных токенов (Blacklist).
-* **Live Directory:** Список активных стримов для Главной страницы обновляется в Redis каждую секунду. БД не участвует в рендеринге каталога.
diff --git a/docs/Deploy.md b/docs/Deploy.md
deleted file mode 100644
index 7ae89b0..0000000
--- a/docs/Deploy.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# Инфраструктура и Процесс Развертывания (GitOps CI/CD)
-
-
-
->[!IMPORTANT]
-> Инфраструктура STREMO управляется по принципу **GitOps**. Это означает, что конфигурация всего Kubernetes-кластера (Deployment, Service, ConfigMap, Ingress) описана в виде манифестов в репозитории `infra`. Прямые изменения кластера через `kubectl apply` строго запрещены.
-
----
-
-## **1. CI/CD Пайплайн (Continuous Integration / Deployment)**
-
-Мы используем двухконтурный процесс: GitLab CI (или GitHub Actions) для сборки кода (CI) и ArgoCD / FluxCD для синхронизации состояния кластера (CD).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Dev("Developer"):::solidBlack -->|"1. Push Code"| AppRepo("App Repo\n(C++ / Go)"):::storage
-
- AppRepo -->|"2. Trigger CI"| CI("GitLab CI\n(Build, Test, Lint)"):::solidBlack
- CI -->|"3. Push Image"| Registry("Docker Registry\n(ECR / Harbor)"):::storage
-
- CI -->|"4. Update Image Tag"| InfraRepo("Infra Repo\n(Kustomize / Helm)"):::storage
-
- ArgoCD("ArgoCD\n(In Kubernetes)"):::dashedBlack -.->|"5. Detect Drift (Pull)"| InfraRepo
-
- ArgoCD ==>|"6. Sync Manifests"| K8s("Production K8s Cluster"):::storage
-```
-
-### **1.1. Этап CI (Сборка)**
-1. При пуше в ветку `main` микросервиса запускается пайплайн.
-2. Запускаются Unit-тесты и статический анализатор кода (SonarQube).
-3. Собирается Docker-образ. В качестве базового образа для C++ микросервисов используется `alpine` или `distroless` для минимизации размера и поверхности атак.
-4. Собранный образ пушится в Container Registry с тегом коммита (например, `v1.2.4-a1b2c3d`).
-5. CI-скрипт автоматически делает коммит в инфраструктурный репозиторий `infra`, заменяя старый тег образа на новый в файлах Kustomize.
-
-### **1.2. Этап CD (Доставка)**
-ArgoCD (или аналогичный GitOps агент), запущенный внутри самого кластера Kubernetes, раз в минуту опрашивает репозиторий `infra`. Как только он видит новый коммит (обновился тег образа), он автоматически применяет эти изменения к кластеру, запуская Rolling Update.
-
----
-
-## **2. Управление Средами (Environments)**
-
-Проект `infra` структурирован с использованием **Kustomize** (встроен в kubectl). Это позволяет иметь общую базу (base) для всех сервисов и накладывать патчи (overlays) для конкретных сред.
-
-Структура директорий:
-```text
-infra/
-├── apps/
-│ ├── base/
-│ │ ├── auth-service/ (deployment.yaml, svc.yaml)
-│ │ ├── chat-service/
-│ └── overlays/
-│ ├── dev/ (Меняет replicas: 1, ресурсы CPU/RAM)
-│ ├── stage/ (Подключает тестовую БД)
-│ └── prod/ (Меняет replicas: 10, подключает HPA, Production БД)
-```
-
->[!NOTE]
-> Среда `stage` (Staging) является точной зеркальной копией `prod` (по архитектуре, но не по мощностям). На нее направляется копия продакшен-трафика (Shadow Traffic) через Ingress Nginx для проверки сервисов под реальной нагрузкой перед релизом в прод.
-
----
-
-## **3. Стратегия релиза без простоев (Zero-Downtime Deployment)**
-
-Поскольку STREMO — это платформа реального времени, отключение API даже на 5 секунд приведет к разрыву стримов. Мы применяем стратегии постепенного выкатывания.
-
-### **3.1. Canary Deployments (Канареечные релизы)**
-
-Для критически важных компонентов (например, API Gateway или Auth Service) используется Canary-выкладка (через Argo Rollouts).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Ingress("Ingress Controller"):::solidBlack
-
- subgraph Deployment Target
- direction LR
- Stable("Stable Pods (v1.0)\n[90% Traffic]"):::storage
- Canary("Canary Pods (v1.1)\n[10% Traffic]"):::dashedBlack
- end
-
- Ingress -->|"Splits Traffic"| Stable
- Ingress -->|"Splits Traffic"| Canary
-
- Metrics("Prometheus\n(Monitors Error Rate 5xx)"):::solidBlack -.->|"Checks Health"| Canary
-
- Metrics -->|"If OK: Promote to 100%\nIf Fail: Rollback to v1.0"| Argo("Argo Rollouts"):::solidBlack
-```
-
-**Процесс:**
-1. ArgoCD запускает всего несколько подов с новой версией приложения (v1.1).
-2. Ingress-контроллер перенаправляет на новые поды только 5-10% пользовательского трафика.
-3. Система ждет 5 минут и анализирует метрики в Prometheus. Если количество ошибок HTTP 5xx на новых подах не превышает норму, трафик увеличивается до 50%, а затем до 100%.
-4. Старые поды (v1.0) уничтожаются.
-
->[!CAUTION]
-> **Ограничение для WebSocket**
-> Канареечные релизы сложны для `chat-service` (WebSockets). Существующие WS-соединения не могут быть плавно перенесены. Для чат-подов используется стратегия медленного Rolling Update: поды убиваются по одному. Клиент (браузер) замечает разрыв, использует экспоненциальную задержку (Exponential Backoff) и переподключается уже к новому, обновленному поду.
diff --git a/docs/Sharding.md b/docs/Sharding.md
deleted file mode 100644
index 98cc14c..0000000
--- a/docs/Sharding.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Стратегия Шардирования (Sharding Strategy)
-
-
-
->[!IMPORTANT]
-> В высоконагруженных стриминговых системах объем генерируемых данных (особенно в чатах трансляций) мгновенно превышает возможности вертикального масштабирования (Scale-Up). Данный документ описывает механизмы горизонтального масштабирования (Scale-Out) с использованием кластера **PostgreSQL + Citus Data**.
-
----
-
-## **1. Архитектура Распределенной Базы Данных Citus**
-
-Citus превращает PostgreSQL в распределенную СУБД, состоящую из одного Coordinator-узла и множества Worker-узлов. Приложение (наши микросервисы) общается только с Coordinator-ом, думая, что работает с обычной локальной базой Postgres.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 100, "nodeSpacing": 30}}}%%
-flowchart TD
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- API("Microservices\n(Chat, VOD, Stream Meta)"):::dashedBlack
-
- subgraph Citus Cluster
- Coordinator("Coordinator Node\n(Routing & Planning)"):::solidBlack
-
- Worker1("Worker Node 1\n(Shards: 1001-1050)"):::storage
- Worker2("Worker Node 2\n(Shards: 1051-1100)"):::storage
- Worker3("Worker Node N\n(Shards: 1101-1150)"):::storage
- end
-
- API -->|"Standard SQL Queries"| Coordinator
-
- Coordinator ==>|"Distributed Query Execution"| Worker1
- Coordinator ==>|"Distributed Query Execution"| Worker2
- Coordinator ==>|"Distributed Query Execution"| Worker3
-```
-
-### **1.1. Маршрутизация запросов**
-Когда `chat-service` отправляет запрос `SELECT * FROM chat_messages WHERE channel_id = 'user-123'`, Coordinator вычисляет хэш от `user-123`, определяет, на каком Worker-узле находится нужный шард, и прозрачно проксирует запрос именно туда, минуя остальные сервера.
-
----
-
-## **2. Выбор Ключа Распределения (Sharding Key)**
-
-Фундаментальное правило шардирования: **данные, которые запрашиваются вместе, должны лежать вместе (на одном физическом диске)**.
-
-Мы используем `channel_id` (ID стримера) в качестве единого ключа распределения (Distribution Column) для всех высоконагруженных таблиц (чаты, статистика, клипы). Это позволяет Citus выполнять операции `JOIN` локально на Worker-узлах без необходимости пересылать терабайты данных по сети (Network Shuffle).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 15}}}%%
-flowchart LR
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- subgraph Logical Concept: Tenant Isolation
- direction TB
- TenantA("Channel A (channel_id=123)"):::solidBlack
- TenantB("Channel B (channel_id=456)"):::solidBlack
- end
-
- subgraph Physical Disk: Worker 1
- direction TB
- DataA("chat_messages (ch_id=123)\nstreams (ch_id=123)\nvods (ch_id=123)"):::storage
- end
-
- subgraph Physical Disk: Worker 2
- direction TB
- DataB("chat_messages (ch_id=456)\nstreams (ch_id=456)\nvods (ch_id=456)"):::storage
- end
-
- TenantA -->|"Hashes to Shard X"| DataA
- TenantB -->|"Hashes to Shard Y"| DataB
-```
-
->[!TIP]
-> **Co-location (Совместное размещение)**
-> Запрос вида `SELECT c.text, s.title FROM chat_messages c JOIN streams s ON c.channel_id = s.channel_id WHERE c.channel_id = '123'` выполнится за миллисекунды, так как обе таблицы для этого канала физически лежат на `Worker 1`.
-
----
-
-## **3. Reference Tables (Справочные таблицы)**
-
-Некоторые таблицы (например, `categories` со списком игр или `badges` со значками модераторов) нужны часто, но они слишком малы для шардирования по ключу.
-
-В Citus они помечаются как **Reference Tables**. Координатор автоматически создает полную копию (реплику) таких таблиц на **каждом** Worker-узле. Это позволяет делать JOIN шардированных таблиц (чат) со справочными (значки) без сетевых задержек.
-
----
-
-## **4. Решение проблемы Hotspots (Горячие точки)**
-
-Самая опасная ситуация при шардировании по `channel_id` — запуск стрима топовым киберспортсменом с онлайном 300,000 зрителей. В этот момент один конкретный `channel_id` сгенерирует 90% нагрузки всей платформы, и Worker, на котором лежит его шард, может упасть (Hotspot).
-
-### **4.1. Смягчение нагрузки (Mitigation Strategies)**
-
-1. **Batching на уровне приложения:** `chat-service` не пишет сообщения в БД поштучно. Он аккумулирует их в RAM и делает один Bulk Insert раз в 500мс. Даже 10,000 сообщений превратятся всего в 2 запроса к Worker-узлу.
-2. **Tenant Isolation (Изоляция тенанта):** С помощью функции `citus_isolate_tenant()` мы можем на лету "вырезать" шард крупного стримера из общего Worker-узла и перенести его на отдельный, выделенный сверхмощный сервер без остановки записи.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 20}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- subgraph Before Isolation
- WorkerA("Worker 1\n(Channels: A, B, C)"):::storage
- end
-
- Admin("DBA / Automation"):::solidBlack -->|"citus_isolate_tenant('channel_A')"| Citus
-
- subgraph After Isolation
- WorkerA_New("Worker 1\n(Channels: B, C)"):::storage
- Worker_Dedicated("Worker 2 (Dedicated)\n(Channel A)"):::dashedBlack
- end
-```
-
->[!CAUTION]
-> **Мониторинг шардов**
-> SRE команда обязана настроить алерты в Grafana на CPU/IOPS каждого отдельного Citus Worker. Если утилизация диска (IO Wait) на одном воркере превышает 80% в течение 2 минут, запускается скрипт автоматической изоляции горячего шарда.
diff --git a/docs/User.md b/docs/User.md
deleted file mode 100644
index 72af049..0000000
--- a/docs/User.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Жизненный Цикл Пользователя и Ролевая Модель
-
-
-
->[!IMPORTANT]
-> Данный документ описывает абстракцию "Пользователь" (User) с точки зрения архитектуры платформы. Он включает диаграмму состояний учетной записи (State Machine), сессионный менеджмент и ролевую модель доступа (RBAC).
-
----
-
-## **1. Модель Состояний (State Machine)**
-
-Пользователь в системе не статичен. В зависимости от уровня верификации и нарушений правил, его аккаунт переходит между жестко заданными состояниями. Маршрутизация на API Gateway (BFF) проверяет текущее состояние пользователя при каждом запросе.
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn"}}%%
-stateDiagram-v2
- classDef default fill:#fff,stroke:#000,stroke-width:2px,color:#000
- classDef danger fill:#fff,stroke:#d63031,stroke-width:2px,stroke-dasharray: 6 6,color:#d63031,rx:15,ry:15
-
- [*] --> Anonymous : Вход на сайт
- Anonymous --> Unverified : Регистрация (POST /register)
-
- Unverified --> Verified : Подтверждение Email / Mobile ID
- Verified --> Affiliate : Выполнение условий платформы
- Affiliate --> Partner : Контракт со STREMO
-
- Verified --> Banned : Нарушение правил (Модерация)
- Affiliate --> Banned : Нарушение правил
- Partner --> Banned : Разрыв контракта / Нарушение
-
- Banned --> Verified : Апелляция одобрена
- Banned --> Deleted : Перманентный бан (Hard Delete)
-
- Verified --> Deleted : Пользователь удалил аккаунт
- Deleted --> [*]
-```
-
-### **Описание состояний:**
-* **Anonymous:** Гость. Имеет доступ к чтению каталога (`GET /streams/live`) и просмотру трансляций. Может читать чат через WS, но не может писать.
-* **Unverified:** Зарегистрированный аккаунт без подтвержденного Email/Телефона. Запрещен запуск трансляций и совершение покупок (защита от бот-сетей).
-* **Verified:** Полноценный пользователь. Может донатить, писать в чат и запускать базовые стримы без возможности монетизации (без рекламы).
-* **Affiliate / Partner:** Статусы стримеров. Открывают доступ к выплатам, кнопке подписки на канал (Tier 1/2/3) и кастомным смайликам.
-* **Banned:** Доступ к API заблокирован на уровне Ingress (код `403 Forbidden`). При попытке входа или обновления токена запрос отклоняется.
-
----
-
-## **2. Ролевая Модель Контроля Доступа (RBAC)**
-
-Авторизация реализована на основе ролей (Role-Based Access Control). Роли вшиваются в полезную нагрузку (Payload) JWT-токена при входе в систему (вызов `POST /login`).
-
-### **2.1. Иерархия Ролей**
-
-| Роль (Role) | Глобальные права | Локальные права (Channel Scope) |
-| :--- | :--- | :--- |
-| `user` | Базовый доступ. | Нет. |
-| `moderator` | Нет глобальных прав. | Имеет право выдавать таймауты и баны (`POST /ban`) только в чате того стримера, который назначил его модератором. |
-| `admin` | Чтение логов, блокировка пользователей. | Может управлять любым каналом. |
-| `superadmin` | Изменение конфигураций платформы. | Полный доступ. |
-
-### **2.2. JWT и Scope-базированая Авторизация**
-
-Чтобы микросервисы (например, `chat-service`) не делали запрос в БД для проверки прав при каждом сообщении пользователя, вся информация криптографически подписана внутри JWT токена.
-
-Пример расшифрованного Payload:
-```json
-{
- "sub": "usr-12345",
- "username": "viewer99",
- "status": "verified",
- "global_roles": ["user"],
- "channel_roles": {
- "channel_id_789": ["moderator"],
- "channel_id_101": ["vip"]
- },
- "exp": 1716249600
-}
-```
-
->[!TIP]
-> **Проверка прав в Микросервисах**
-> Когда пользователь отправляет запрос на бан другого зрителя (эндпоинт `/moderation/channels/789/ban`), микросервис Модерации просто извлекает JWT заголовок, парсит его (проверяя подпись) и смотрит, есть ли у пользователя роль `moderator` в массиве `channel_roles["channel_id_789"]`. Дополнительного похода в БД не требуется.
-
----
-
-## **3. Менеджмент Сессий и Безопасность**
-
-Платформа использует паттерн **Short-Lived Access Token + Long-Lived Refresh Token**.
-
-1. **Access Token (JWT):** Живет всего 15 минут. Хранится в памяти браузера фронтенда (не в LocalStorage, чтобы избежать XSS атак). Отправляется в каждом запросе в заголовке `Authorization: Bearer`.
-2. **Refresh Token:** Живет 30 дней. Хранится в браузере в виде `HttpOnly; Secure; SameSite=Strict` Cookie. Недоступен для JavaScript.
-
-### **3.1. Отзыв сессий (Token Revocation & Blacklisting)**
-
-Классическая проблема JWT — невозможность отозвать токен до истечения его срока жизни (15 минут). Если пользователь меняет пароль или его банит администратор, злоумышленник может использовать украденный Access Token еще 15 минут.
-
-**Решение STREMO (Redis Blacklist):**
-* При сбросе пароля или бане, `user_id` пользователя помещается в Redis с ключом `blacklist:usr-12345` и TTL = 15 минут.
-* API Gateway (BFF) перед пересылкой запроса внутренним сервисам быстро (за 1-2 мс) проверяет наличие пользователя в Redis Blacklist. Если он там есть — запрос отклоняется с `401 Unauthorized`, заставляя клиента запросить новый токен (который не выдадут из-за измененного пароля/бана).
-
-```mermaid
-%%{init: {"theme": "base", "look": "handDrawn", "flowchart": {"rankSpacing": 80, "nodeSpacing": 15}}}%%
-flowchart LR
- classDef dashedBlack fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray: 6 6,color:#000,rx:15,ry:15
- classDef solidBlack fill:#fff,stroke:#000,stroke-width:2px,color:#000,rx:15,ry:15
- classDef storage fill:#fdfdfd,stroke:#555,stroke-width:2px,color:#000,rx:5,ry:5
-
- Client("Web Client"):::solidBlack -->|"1. API Request + JWT"| Gateway("API Gateway\n(Token Introspection)"):::dashedBlack
-
- Gateway -->|"2. Check Revoked?"| Redis("Redis\n(Blacklist Cache)"):::storage
-
- Redis -.->|"3. Not Revoked"| Gateway
-
- Gateway ==>|"4. Proxy Request"| TargetSvc("Target Microservice\n(e.g., Billing)"):::solidBlack
-```
-
->[!CAUTION]
-> **Безопасность Транзакций**
-> Любые операции по списанию средств (донат, подписка) требуют обязательной валидации сессии на уровне `billing-service`. Даже если JWT прошел валидацию на Gateway, биллинг повторно проверит целостность аккаунта в глобальной БД перед транзакцией.
diff --git a/docs/srs/Terms-of-Reference-STREMO.tex b/docs/srs/Terms-of-Reference-STREMO.tex
deleted file mode 100644
index 561ad3f..0000000
--- a/docs/srs/Terms-of-Reference-STREMO.tex
+++ /dev/null
@@ -1,107 +0,0 @@
-\documentclass[12pt,a4paper]{article}
-
-\usepackage{fontspec}
-\setmainfont{CMU Serif}
-\setsansfont{CMU Sans Serif}
-\setmonofont{CMU Typewriter Text}
-\usepackage[russian]{babel}
-
-\usepackage{amsmath}
-\usepackage{amssymb}
-\usepackage{amsfonts}
-
-\usepackage{graphicx}
-\usepackage{geometry}
-\geometry{top=3cm, bottom=2.5cm, left=3cm, right=2cm, headheight=1.5cm, headsep=0.8cm}
-
-\usepackage{fancyhdr}
-\pagestyle{fancy}
-\fancyhf{}
-
-\fancyhead[L]{\includegraphics[height=1.2cm]{../img/logo.png}}
-\fancyhead[R]{\small\scshape STREMO: Математические модели}
-\fancyfoot[C]{\thepage}
-\renewcommand{\headrulewidth}{0.4pt}
-\renewcommand{\footrulewidth}{0pt}
-
-\usepackage{titlesec}
-\titleformat{\section}{\Large\bfseries}{\thesection.}{0.5em}{}
-\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{0.5em}{}
-
-\begin{document}
-
-\thispagestyle{fancy}
-
-\begin{center}
- \vspace{0.4cm}
- \Large{\textbf{Спецификация математических моделей и алгоритмов обработки}}\\
- \vspace{0.5cm}
- \rule{\textwidth}{0.4pt}
-\end{center}
-
-\vspace{1cm}
-
-\section{1. Оптимизация размера медиа-чанка (Streaming Pipeline)}
-
-Для балансировки задержки `Latency` и пропускной способности `Bandwidth` при передаче живого видеопотока по протоколу gRPC необходимо аналитически определить оптимальный размер медиа-чанка $S_{chunk}$ в битах.
-
-Введем обозначения:
-\begin{itemize}
- \item $B_{network}$ --- доступная пропускная способность канала связи (бит/с).
- \item $R_{bitrate}$ --- битрейт видеопотока (бит/с), при условии $B_{network} > R_{bitrate}$.
- \item $T_{overhead}$ --- константная задержка на сериализацию Protobuf и отправку сетевого кадра (с).
-\end{itemize}
-
-Длительность контента, содержащегося в одном чанке, вычисляется как $$D_{chunk} = \frac{S_{chunk}}{R_{bitrate}}$$.\\
-Время физической передачи данного чанка по сети составляет $$T_{tx} = \frac{S_{chunk}}{B_{network}} + T_{overhead}$$.
-
-Фундаментальное условие непрерывности трансляции (исключение опустошения буфера) требует, чтобы время передачи чанка не превышало его медийную длительность:
-\begin{equation}
-T_{tx} \le D_{chunk} \implies \frac{S_{chunk}}{B_{network}} + T_{overhead} \le \frac{S_{chunk}}{R_{bitrate}}
-\end{equation}
-
-Решая данное неравенство относительно искомого размера чанка $S_{chunk}$, получаем:
-\begin{equation}
-S_{chunk} \left( \frac{1}{R_{bitrate}} - \frac{1}{B_{network}} \right) \ge T_{overhead}
-\end{equation}
-\begin{equation}
-S_{chunk} \ge \frac{T_{overhead} \cdot R_{bitrate} \cdot B_{network}}{B_{network} - R_{bitrate}}
-\end{equation}
-
-Поскольку end-to-end задержка прямо пропорциональна размеру чанка ($L \approx D_{chunk} + T_{tx}$), для ее минимизации размер чанка фиксируется на строгой нижней границе:
-\begin{equation}
-S_{opt} = \frac{T_{overhead} \cdot R_{bitrate} \cdot B_{network}}{B_{network} - R_{bitrate}}
-\end{equation}
-
-\section{2. Динамическая модель буферизации (ABR)}
-
-Размер видеобуфера $Q(t)$ на стороне клиента в момент времени $t$ описывается обыкновенным дифференциальным уравнением:
-\begin{equation}
-\frac{dQ(t)}{dt} = \frac{C(t)}{R_v(t)} - 1
-\end{equation}
-где $C(t)$ --- текущая пропускная способность сети, а $R_v(t)$ --- битрейт выбранного слоя качества. Целевая функция алгоритма адаптивного битрейта (ABR) сводится к максимизации качества при жестком ограничении вероятности опустошения буфера:
-\begin{equation}
-\max_{R_v} \sum_{k=1}^{K} U(R_v(k)) - \lambda \sum_{k=1}^{K-1} |R_v(k+1) - R_v(k)|
-\end{equation}
-при условии $Q(t) > 0$, где $U$ --- функция полезности, а $\lambda$ --- весовой коэффициент штрафа за переключение качества.
-
-\section{3. Матричные преобразования цветового пространства}
-
-В процессе транскодирования видеопотока (переход YUV $\leftrightarrow$ RGB) применяется линейное преобразование цветовых метрик. В рамках математического аппарата системы STREMO вектор состояния пикселя $\mathbf{v}$ строго определяется как \textbf{вектор-столбец}:
-\begin{equation}
-\mathbf{v} = \begin{pmatrix} v_1 \\ v_2 \\ v_3 \end{pmatrix}
-\end{equation}
-
-Прямое преобразование из исходного цветового пространства задается операцией левостороннего умножения на матрицу преобразования $A$:
-\begin{equation}
-\mathbf{c} = A \mathbf{v}
-\end{equation}
-
-Для обратного вычисления исходных компонент применяется левостороннее умножение вектора-столбца $\mathbf{c}$ на обратную матрицу $A^{-1}$:
-\begin{equation}
-\mathbf{v} = A^{-1} \mathbf{c}
-\end{equation}
-
-Обратите внимание: операция транспонирования обратной матрицы в данном случае \textbf{не применяется}. Транспонирование необходимо исключительно при использовании конвенции векторов-строк (где $\mathbf{v}^T = \mathbf{c}^T (A^{-1})^T$). Так как система оперирует исключительно векторами-столбцами, форма $\mathbf{v} = A^{-1} \mathbf{c}$ сохраняет полную математическую корректность.
-
-\end{document}
\ No newline at end of file
diff --git a/docs/srs/Terms_of_Reference_STREMO.pdf b/docs/srs/Terms_of_Reference_STREMO.pdf
deleted file mode 100644
index c0e0747..0000000
Binary files a/docs/srs/Terms_of_Reference_STREMO.pdf and /dev/null differ
diff --git a/frontend/.gitignore b/frontend/.gitignore
deleted file mode 100644
index 5ef6a52..0000000
--- a/frontend/.gitignore
+++ /dev/null
@@ -1,41 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# env files (can opt-in for committing if needed)
-.env*
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
diff --git a/frontend/README.md b/frontend/README.md
deleted file mode 100644
index e215bc4..0000000
--- a/frontend/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
-
-## Getting Started
-
-First, run the development server:
-
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
-
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
-
-## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs
deleted file mode 100644
index 05e726d..0000000
--- a/frontend/eslint.config.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-import { defineConfig, globalIgnores } from "eslint/config";
-import nextVitals from "eslint-config-next/core-web-vitals";
-import nextTs from "eslint-config-next/typescript";
-
-const eslintConfig = defineConfig([
- ...nextVitals,
- ...nextTs,
- // Override default ignores of eslint-config-next.
- globalIgnores([
- // Default ignores of eslint-config-next:
- ".next/**",
- "out/**",
- "build/**",
- "next-env.d.ts",
- ]),
-]);
-
-export default eslintConfig;
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
deleted file mode 100644
index 0b1ec0b..0000000
--- a/frontend/next.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { NextConfig } from "next";
-
-const nextConfig: NextConfig = {
- reactCompiler: true,
-};
-
-export default nextConfig;
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
deleted file mode 100644
index 024e862..0000000
--- a/frontend/package-lock.json
+++ /dev/null
@@ -1,6714 +0,0 @@
-{
- "name": "frontend",
- "version": "0.1.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "frontend",
- "version": "0.1.0",
- "dependencies": {
- "lucide-react": "^1.14.0",
- "next": "16.2.5",
- "react": "19.2.4",
- "react-dom": "19.2.4"
- },
- "devDependencies": {
- "@tailwindcss/postcss": "^4",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "babel-plugin-react-compiler": "1.0.0",
- "eslint": "^9",
- "eslint-config-next": "16.2.5",
- "tailwindcss": "^4",
- "typescript": "^5"
- }
- },
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
- "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.29.3",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
- "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
- "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/traverse": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.29.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
- "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
- "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.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-module-imports": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
- "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
- "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
- "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.29.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
- "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
- "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
- "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.0",
- "@babel/generator": "^7.29.0",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.29.0",
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.29.0",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@emnapi/core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
- "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
- "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
- "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.5"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
- "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.14.0",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.1",
- "minimatch": "^3.1.5",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.4",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
- "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
- "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/types": "^0.15.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.8",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
- "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.2",
- "@humanfs/types": "^0.15.0",
- "@humanwhocodes/retry": "^0.4.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/types": {
- "version": "0.15.0",
- "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
- "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@img/colour": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
- "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
- "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
- "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
- "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
- "cpu": [
- "arm"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
- "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-ppc64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
- "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-riscv64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
- "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
- "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
- "cpu": [
- "s390x"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
- "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
- "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
- "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
- "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
- "cpu": [
- "arm"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
- "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
- "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
- "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
- "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
- "cpu": [
- "s390x"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
- "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
- "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
- "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
- "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.7.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
- "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
- "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
- "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "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/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "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.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
- "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.4.3",
- "@emnapi/runtime": "^1.4.3",
- "@tybys/wasm-util": "^0.10.0"
- }
- },
- "node_modules/@next/env": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.5.tgz",
- "integrity": "sha512-Lb9ElHD2klcyeVD25vW+siPFqz9QMzDUSgvFZNO+dZEKoMHex4viJhVuzBhrXKqb+UKnih7mVYbt50/7KLsSCA==",
- "license": "MIT"
- },
- "node_modules/@next/eslint-plugin-next": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.5.tgz",
- "integrity": "sha512-PyILm/cw2u5gEG5xOjqFbALUAl/erAqtM47iZtP9lXiSzin+eOIf3KRi+CBC/mFG9j7Iz3JDqCOY94nFLUCccg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-glob": "3.3.1"
- }
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.5.tgz",
- "integrity": "sha512-BW+8PGVmsruomXHsitD8JG6gny9lEdobctjBwvtPF8AKtxGDR7nR35FOl/oK9UAPXBOBm+vx0k8qtpeHOXQMGQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.5.tgz",
- "integrity": "sha512-ZoCGnCl9LlQJWmqXrZAUlNxvuNmclvE+7zUif+nDydkkehl9FKxHJ+wxSQMj+C37BYFerKiEdX9s9o02ir975Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.5.tgz",
- "integrity": "sha512-AwcZzMChaWkOTZt3vu+2ZMIj8g4dYQY+B8VUVhlFSQ2JtvyZpefyYHTe00D6b6L7BysYw7vl3zsvs9jix8tl5Q==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.5.tgz",
- "integrity": "sha512-QqMgqWbCBFsfiQ7BF3dUlW8HJy1LWhpcqbTpoHMWA9IV+TnWwDKozQJA5NdIAHjQ00yX2Q7AUkLr/XK4n77q8A==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.5.tgz",
- "integrity": "sha512-3hzeiFGZtyATVx9pCeuzTshXmh50vHZitqaeZiyJZaUmjQyrfjsVUgS8apOj1vEJCIpKJM/55F45yPAV2kpjsA==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.5.tgz",
- "integrity": "sha512-0mzZV/mAt7Qj2tYNdTB6AqrS8dwng/AQLSYC5Z1YLpZdi2wxqKDPK7RY2RvjB1fXyJfOfdA3l/yTF5yLi+WfuQ==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.5.tgz",
- "integrity": "sha512-f/H4nZ2zJBvA8/+HpsB9mNonF9zfQoAU6D0WxJrfzhJDvJLfngVN85oqxUyrDVK99DIFfFYhLpGa5K+c5uotSw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.5.tgz",
- "integrity": "sha512-nuP7DHs4koAojsIxVPkihNgKiRUKtCU65j5X6DAbSy8VBrfT/o90bCLLHPf51JEdOZwZMFzM6e0NiGWfIWjVAg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nolyfill/is-core-module": {
- "version": "1.0.39",
- "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
- "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.4.0"
- }
- },
- "node_modules/@rtsao/scc": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
- "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "enhanced-resolve": "^5.19.0",
- "jiti": "^2.6.1",
- "lightningcss": "1.32.0",
- "magic-string": "^0.30.21",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.2.4"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz",
- "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 20"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.2.4",
- "@tailwindcss/oxide-darwin-arm64": "4.2.4",
- "@tailwindcss/oxide-darwin-x64": "4.2.4",
- "@tailwindcss/oxide-freebsd-x64": "4.2.4",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
- "@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
- "@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
- "@tailwindcss/oxide-linux-x64-musl": "4.2.4",
- "@tailwindcss/oxide-wasm32-wasi": "4.2.4",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
- "@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
- "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
- "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
- "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
- "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
- "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
- "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
- "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
- "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
- "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
- "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.8.1",
- "@emnapi/runtime": "^1.8.1",
- "@emnapi/wasi-threads": "^1.1.0",
- "@napi-rs/wasm-runtime": "^1.1.1",
- "@tybys/wasm-util": "^0.10.1",
- "tslib": "^2.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
- "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz",
- "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/postcss": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.4.tgz",
- "integrity": "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.2.4",
- "@tailwindcss/oxide": "4.2.4",
- "postcss": "^8.5.6",
- "tailwindcss": "4.2.4"
- }
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.2",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
- "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "20.19.39",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
- "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.14",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
- "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz",
- "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/type-utils": "8.59.2",
- "@typescript-eslint/utils": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
- "ignore": "^7.0.5",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.5.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.59.2",
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz",
- "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
- "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.59.2",
- "@typescript-eslint/types": "^8.59.2",
- "debug": "^4.4.3"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
- "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
- "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz",
- "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/utils": "8.59.2",
- "debug": "^4.4.3",
- "ts-api-utils": "^2.5.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
- "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
- "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.59.2",
- "@typescript-eslint/tsconfig-utils": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
- "debug": "^4.4.3",
- "minimatch": "^10.2.2",
- "semver": "^7.7.3",
- "tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.5.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz",
- "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
- "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.59.2",
- "eslint-visitor-keys": "^5.0.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
- "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@unrs/resolver-binding-android-arm-eabi": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
- "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-android-arm64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
- "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-arm64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
- "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-x64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
- "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-freebsd-x64": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
- "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
- "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
- "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
- "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
- "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
- "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
- "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
- "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
- "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
- "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-musl": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
- "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-wasm32-wasi": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
- "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^0.2.11"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
- "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
- "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
- "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
- "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/array-buffer-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
- "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "is-array-buffer": "^3.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array-includes": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
- "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.24.0",
- "es-object-atoms": "^1.1.1",
- "get-intrinsic": "^1.3.0",
- "is-string": "^1.1.1",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.findlast": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
- "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.findlastindex": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
- "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "es-shim-unscopables": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flat": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
- "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flatmap": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
- "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.tosorted": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
- "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3",
- "es-errors": "^1.3.0",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/arraybuffer.prototype.slice": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
- "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.1",
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "is-array-buffer": "^3.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ast-types-flow": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
- "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/async-function": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
- "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/available-typed-arrays": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
- "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "possible-typed-array-names": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/axe-core": {
- "version": "4.11.4",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz",
- "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==",
- "dev": true,
- "license": "MPL-2.0",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/babel-plugin-react-compiler": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
- "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.26.0"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.10.27",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz",
- "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==",
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.cjs"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
- "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.10.12",
- "caniuse-lite": "^1.0.30001782",
- "electron-to-chromium": "^1.5.328",
- "node-releases": "^2.0.36",
- "update-browserslist-db": "^1.2.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/call-bind": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
- "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "get-intrinsic": "^1.3.0",
- "set-function-length": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001792",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
- "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT"
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/damerau-levenshtein": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
- "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
- "dev": true,
- "license": "BSD-2-Clause"
- },
- "node_modules/data-view-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
- "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/data-view-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
- "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/inspect-js"
- }
- },
- "node_modules/data-view-byte-offset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
- "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/define-properties": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
- "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.352",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.352.tgz",
- "integrity": "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/enhanced-resolve": {
- "version": "5.21.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
- "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/es-abstract": {
- "version": "1.24.2",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
- "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.2",
- "arraybuffer.prototype.slice": "^1.0.4",
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "data-view-buffer": "^1.0.2",
- "data-view-byte-length": "^1.0.2",
- "data-view-byte-offset": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "es-set-tostringtag": "^2.1.0",
- "es-to-primitive": "^1.3.0",
- "function.prototype.name": "^1.1.8",
- "get-intrinsic": "^1.3.0",
- "get-proto": "^1.0.1",
- "get-symbol-description": "^1.1.0",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "internal-slot": "^1.1.0",
- "is-array-buffer": "^3.0.5",
- "is-callable": "^1.2.7",
- "is-data-view": "^1.0.2",
- "is-negative-zero": "^2.0.3",
- "is-regex": "^1.2.1",
- "is-set": "^2.0.3",
- "is-shared-array-buffer": "^1.0.4",
- "is-string": "^1.1.1",
- "is-typed-array": "^1.1.15",
- "is-weakref": "^1.1.1",
- "math-intrinsics": "^1.1.0",
- "object-inspect": "^1.13.4",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.7",
- "own-keys": "^1.0.1",
- "regexp.prototype.flags": "^1.5.4",
- "safe-array-concat": "^1.1.3",
- "safe-push-apply": "^1.0.0",
- "safe-regex-test": "^1.1.0",
- "set-proto": "^1.0.0",
- "stop-iteration-iterator": "^1.1.0",
- "string.prototype.trim": "^1.2.10",
- "string.prototype.trimend": "^1.0.9",
- "string.prototype.trimstart": "^1.0.8",
- "typed-array-buffer": "^1.0.3",
- "typed-array-byte-length": "^1.0.3",
- "typed-array-byte-offset": "^1.0.4",
- "typed-array-length": "^1.0.7",
- "unbox-primitive": "^1.1.0",
- "which-typed-array": "^1.1.19"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-iterator-helpers": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz",
- "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.9",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.24.2",
- "es-errors": "^1.3.0",
- "es-set-tostringtag": "^2.1.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.3.0",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "iterator.prototype": "^1.1.5",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-shim-unscopables": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
- "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-to-primitive": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
- "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.2.7",
- "is-date-object": "^1.0.5",
- "is-symbol": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.39.4",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
- "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.2",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.5",
- "@eslint/js": "9.39.4",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.14.0",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.5",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-config-next": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.5.tgz",
- "integrity": "sha512-fXEkugikngux1FBJ/Vop+52SLAMFjXZFXjyl/+HjGHngnXf8iIfqe3qdjcwN+40RBpSsCVhI04j0/ngEWL5Qng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@next/eslint-plugin-next": "16.2.5",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.32.0",
- "eslint-plugin-jsx-a11y": "^6.10.0",
- "eslint-plugin-react": "^7.37.0",
- "eslint-plugin-react-hooks": "^7.0.0",
- "globals": "16.4.0",
- "typescript-eslint": "^8.46.0"
- },
- "peerDependencies": {
- "eslint": ">=9.0.0",
- "typescript": ">=3.3.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-config-next/node_modules/globals": {
- "version": "16.4.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
- "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint-import-resolver-node": {
- "version": "0.3.10",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz",
- "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7",
- "is-core-module": "^2.16.1",
- "resolve": "^2.0.0-next.6"
- }
- },
- "node_modules/eslint-import-resolver-node/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-import-resolver-typescript": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
- "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@nolyfill/is-core-module": "1.0.39",
- "debug": "^4.4.0",
- "get-tsconfig": "^4.10.0",
- "is-bun-module": "^2.0.0",
- "stable-hash": "^0.0.5",
- "tinyglobby": "^0.2.13",
- "unrs-resolver": "^1.6.2"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-import-resolver-typescript"
- },
- "peerDependencies": {
- "eslint": "*",
- "eslint-plugin-import": "*",
- "eslint-plugin-import-x": "*"
- },
- "peerDependenciesMeta": {
- "eslint-plugin-import": {
- "optional": true
- },
- "eslint-plugin-import-x": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
- "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import": {
- "version": "2.32.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
- "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rtsao/scc": "^1.1.0",
- "array-includes": "^3.1.9",
- "array.prototype.findlastindex": "^1.2.6",
- "array.prototype.flat": "^1.3.3",
- "array.prototype.flatmap": "^1.3.3",
- "debug": "^3.2.7",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.9",
- "eslint-module-utils": "^2.12.1",
- "hasown": "^2.0.2",
- "is-core-module": "^2.16.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "object.groupby": "^1.0.3",
- "object.values": "^1.2.1",
- "semver": "^6.3.1",
- "string.prototype.trimend": "^1.0.9",
- "tsconfig-paths": "^3.15.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-jsx-a11y": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
- "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aria-query": "^5.3.2",
- "array-includes": "^3.1.8",
- "array.prototype.flatmap": "^1.3.2",
- "ast-types-flow": "^0.0.8",
- "axe-core": "^4.10.0",
- "axobject-query": "^4.1.0",
- "damerau-levenshtein": "^1.0.8",
- "emoji-regex": "^9.2.2",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^3.3.5",
- "language-tags": "^1.0.9",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "safe-regex-test": "^1.0.3",
- "string.prototype.includes": "^2.0.1"
- },
- "engines": {
- "node": ">=4.0"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-react": {
- "version": "7.37.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
- "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.8",
- "array.prototype.findlast": "^1.2.5",
- "array.prototype.flatmap": "^1.3.3",
- "array.prototype.tosorted": "^1.1.4",
- "doctrine": "^2.1.0",
- "es-iterator-helpers": "^1.2.1",
- "estraverse": "^5.3.0",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.9",
- "object.fromentries": "^2.0.8",
- "object.values": "^1.2.1",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.5",
- "semver": "^6.3.1",
- "string.prototype.matchall": "^4.0.12",
- "string.prototype.repeat": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
- "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.24.4",
- "@babel/parser": "^7.24.4",
- "hermes-parser": "^0.25.1",
- "zod": "^3.25.0 || ^4.0.0",
- "zod-validation-error": "^3.5.0 || ^4.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
- "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
- "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
- "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/for-each": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
- "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.2.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/function.prototype.name": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
- "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "functions-have-names": "^1.2.3",
- "hasown": "^2.0.2",
- "is-callable": "^1.2.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/generator-function": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
- "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/get-symbol-description": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
- "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-tsconfig": {
- "version": "4.14.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
- "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-pkg-maps": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globalthis": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
- "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-properties": "^1.2.1",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/has-bigints": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
- "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
- "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/hermes-estree": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
- "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/hermes-parser": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
- "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hermes-estree": "0.25.1"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/internal-slot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
- "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.2",
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-array-buffer": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
- "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-async-function": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
- "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "async-function": "^1.0.0",
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bigint": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
- "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-bigints": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-boolean-object": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
- "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bun-module": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
- "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.7.1"
- }
- },
- "node_modules/is-bun-module/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.2",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
- "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-data-view": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
- "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "is-typed-array": "^1.1.13"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-date-object": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
- "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-finalizationregistry": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
- "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-generator-function": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
- "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.4",
- "generator-function": "^2.0.0",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-negative-zero": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
- "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-number-object": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
- "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-regex": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
- "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
- "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-string": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
- "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-symbol": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
- "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-symbols": "^1.1.0",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-typed-array": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
- "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakref": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
- "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakset": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
- "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/iterator.prototype": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
- "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "get-proto": "^1.0.0",
- "has-symbols": "^1.1.0",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/jiti": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
- "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsx-ast-utils": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
- "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "object.assign": "^4.1.4",
- "object.values": "^1.1.6"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/language-subtag-registry": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
- "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/language-tags": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
- "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "language-subtag-registry": "^0.3.20"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
- }
- },
- "node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "libc": [
- "musl"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lucide-react": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz",
- "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==",
- "license": "ISC",
- "peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.12",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
- "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/napi-postinstall": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
- "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "napi-postinstall": "lib/cli.js"
- },
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/napi-postinstall"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/next": {
- "version": "16.2.5",
- "resolved": "https://registry.npmjs.org/next/-/next-16.2.5.tgz",
- "integrity": "sha512-TkVTm9F2WEulkgGljm4wPwNgvCCWCVw6StUHsZb8WZpHFRjepoUWg3d7L4IMg7IyjcJ4Co9eVhpro8e8O+KarQ==",
- "license": "MIT",
- "dependencies": {
- "@next/env": "16.2.5",
- "@swc/helpers": "0.5.15",
- "baseline-browser-mapping": "^2.9.19",
- "caniuse-lite": "^1.0.30001579",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.6"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "16.2.5",
- "@next/swc-darwin-x64": "16.2.5",
- "@next/swc-linux-arm64-gnu": "16.2.5",
- "@next/swc-linux-arm64-musl": "16.2.5",
- "@next/swc-linux-x64-gnu": "16.2.5",
- "@next/swc-linux-x64-musl": "16.2.5",
- "@next/swc-win32-arm64-msvc": "16.2.5",
- "@next/swc-win32-x64-msvc": "16.2.5",
- "sharp": "^0.34.5"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.51.1",
- "babel-plugin-react-compiler": "*",
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "node_modules/next/node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "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.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/node-exports-info": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
- "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array.prototype.flatmap": "^1.3.3",
- "es-errors": "^1.3.0",
- "object.entries": "^1.1.9",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.38",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
- "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.assign": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
- "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0",
- "has-symbols": "^1.1.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.entries": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
- "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.fromentries": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
- "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.groupby": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
- "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.values": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
- "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/own-keys": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
- "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.2.6",
- "object-keys": "^1.1.1",
- "safe-push-apply": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/possible-typed-array-names": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
- "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
- "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/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/react": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
- "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
- "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.4"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/reflect.getprototypeof": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
- "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.1",
- "which-builtin-type": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/regexp.prototype.flags": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
- "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-errors": "^1.3.0",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve": {
- "version": "2.0.0-next.6",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
- "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "is-core-module": "^2.16.1",
- "node-exports-info": "^1.6.0",
- "object-keys": "^1.1.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/safe-array-concat": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz",
- "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.9",
- "call-bound": "^1.0.4",
- "get-intrinsic": "^1.3.0",
- "has-symbols": "^1.1.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">=0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safe-push-apply": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
- "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safe-regex-test": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
- "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-regex": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-function-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
- "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "functions-have-names": "^1.2.3",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-proto": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
- "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/sharp": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
- "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "@img/colour": "^1.0.0",
- "detect-libc": "^2.1.2",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.34.5",
- "@img/sharp-darwin-x64": "0.34.5",
- "@img/sharp-libvips-darwin-arm64": "1.2.4",
- "@img/sharp-libvips-darwin-x64": "1.2.4",
- "@img/sharp-libvips-linux-arm": "1.2.4",
- "@img/sharp-libvips-linux-arm64": "1.2.4",
- "@img/sharp-libvips-linux-ppc64": "1.2.4",
- "@img/sharp-libvips-linux-riscv64": "1.2.4",
- "@img/sharp-libvips-linux-s390x": "1.2.4",
- "@img/sharp-libvips-linux-x64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
- "@img/sharp-linux-arm": "0.34.5",
- "@img/sharp-linux-arm64": "0.34.5",
- "@img/sharp-linux-ppc64": "0.34.5",
- "@img/sharp-linux-riscv64": "0.34.5",
- "@img/sharp-linux-s390x": "0.34.5",
- "@img/sharp-linux-x64": "0.34.5",
- "@img/sharp-linuxmusl-arm64": "0.34.5",
- "@img/sharp-linuxmusl-x64": "0.34.5",
- "@img/sharp-wasm32": "0.34.5",
- "@img/sharp-win32-arm64": "0.34.5",
- "@img/sharp-win32-ia32": "0.34.5",
- "@img/sharp-win32-x64": "0.34.5"
- }
- },
- "node_modules/sharp/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
- "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stable-hash": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
- "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/stop-iteration-iterator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
- "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "internal-slot": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/string.prototype.includes": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
- "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/string.prototype.matchall": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
- "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "regexp.prototype.flags": "^1.5.3",
- "set-function-name": "^2.0.2",
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.repeat": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
- "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "node_modules/string.prototype.trim": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
- "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-object-atoms": "^1.0.0",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimend": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
- "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
- "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
- "license": "MIT",
- "dependencies": {
- "client-only": "0.0.1"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tailwindcss": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
- "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
- "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tinyglobby/node_modules/fdir": {
- "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"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
- "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/tsconfig-paths": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
- "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
- },
- "node_modules/tsconfig-paths/node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typed-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/typed-array-byte-length": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
- "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-byte-offset": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
- "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "reflect.getprototypeof": "^1.0.9"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-length": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
- "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "is-typed-array": "^1.1.13",
- "possible-typed-array-names": "^1.0.0",
- "reflect.getprototypeof": "^1.0.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz",
- "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.59.2",
- "@typescript-eslint/parser": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/utils": "8.59.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
- "typescript": ">=4.8.4 <6.1.0"
- }
- },
- "node_modules/unbox-primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
- "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.1.0",
- "which-boxed-primitive": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/unrs-resolver": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
- "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "napi-postinstall": "^0.3.0"
- },
- "funding": {
- "url": "https://opencollective.com/unrs-resolver"
- },
- "optionalDependencies": {
- "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
- "@unrs/resolver-binding-android-arm64": "1.11.1",
- "@unrs/resolver-binding-darwin-arm64": "1.11.1",
- "@unrs/resolver-binding-darwin-x64": "1.11.1",
- "@unrs/resolver-binding-freebsd-x64": "1.11.1",
- "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
- "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
- "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
- "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
- "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
- "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
- "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
- "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
- "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
- "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
- "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
- "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
- "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
- "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/which-boxed-primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
- "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-bigint": "^1.1.0",
- "is-boolean-object": "^1.2.1",
- "is-number-object": "^1.1.1",
- "is-string": "^1.1.1",
- "is-symbol": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-builtin-type": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
- "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "function.prototype.name": "^1.1.6",
- "has-tostringtag": "^1.0.2",
- "is-async-function": "^2.0.0",
- "is-date-object": "^1.1.0",
- "is-finalizationregistry": "^1.1.0",
- "is-generator-function": "^1.0.10",
- "is-regex": "^1.2.1",
- "is-weakref": "^1.0.2",
- "isarray": "^2.0.5",
- "which-boxed-primitive": "^1.1.0",
- "which-collection": "^1.0.2",
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-collection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
- "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-map": "^2.0.3",
- "is-set": "^2.0.3",
- "is-weakmap": "^2.0.2",
- "is-weakset": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-typed-array": {
- "version": "1.1.20",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
- "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "for-each": "^0.3.5",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zod": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
- "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-validation-error": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
- "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- }
- }
- }
-}
diff --git a/frontend/package.json b/frontend/package.json
deleted file mode 100644
index c2d182d..0000000
--- a/frontend/package.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "frontend",
- "version": "0.1.0",
- "private": true,
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "eslint"
- },
- "dependencies": {
- "lucide-react": "^1.14.0",
- "next": "16.2.5",
- "react": "19.2.4",
- "react-dom": "19.2.4"
- },
- "devDependencies": {
- "@tailwindcss/postcss": "^4",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "babel-plugin-react-compiler": "1.0.0",
- "eslint": "^9",
- "eslint-config-next": "16.2.5",
- "tailwindcss": "^4",
- "typescript": "^5"
- }
-}
diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs
deleted file mode 100644
index 61e3684..0000000
--- a/frontend/postcss.config.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-const config = {
- plugins: {
- "@tailwindcss/postcss": {},
- },
-};
-
-export default config;
diff --git a/frontend/public/file.svg b/frontend/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/frontend/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg
deleted file mode 100644
index 567f17b..0000000
--- a/frontend/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/next.svg b/frontend/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/frontend/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg
deleted file mode 100644
index 7705396..0000000
--- a/frontend/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/window.svg b/frontend/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/frontend/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/frontend/src/app/favicon.ico and /dev/null differ
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
deleted file mode 100644
index a57aeae..0000000
--- a/frontend/src/app/globals.css
+++ /dev/null
@@ -1,85 +0,0 @@
-@import "tailwindcss";
-
-:root {
- --background: #000000;
- --foreground: #f4f4f5;
- --card: #1c1d1f;
- --accent-pink: #f20e89;
- --accent-purple: #e473ff;
-}
-
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-pink-primary: var(--accent-pink);
- --color-purple-primary: var(--accent-purple);
-}
-
-body {
- background: var(--background);
- color: var(--foreground);
-}
-
-/* Custom Scrollbar */
-.custom-scrollbar::-webkit-scrollbar { width: 6px; }
-.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
-.custom-scrollbar::-webkit-scrollbar-thumb { background: #27272a; border-radius: 10px; }
-.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #D673A9; }
-
-/* Animations */
-@keyframes dropdownSlide {
- from { opacity: 0; transform: translateY(-10px) scale(0.95); }
- to { opacity: 1; transform: translateY(0) scale(1); }
-}
-
-.animate-dropdown {
- animation: dropdownSlide 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
- transform-origin: top right;
-}
-
-@keyframes cookiePopIn {
- 0% { opacity: 0; transform: translate(-50%, 40px) scale(0.95); }
- 60% { opacity: 1; transform: translate(-50%, -5px) scale(1.02); }
- 100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
-}
-
-.animate-cookie-in {
- animation: cookiePopIn 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
-}
-
-@keyframes cookieFadeOut {
- from { opacity: 1; transform: translate(-50%, 0) scale(1); }
- to { opacity: 0; transform: translate(-50%, 20px) scale(0.95); }
-}
-
-.animate-cookie-out {
- animation: cookieFadeOut 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
-}
-
-/* Stream Player Placeholder Animation */
-@keyframes pulse-bg {
- 0%, 100% { background-color: #27272a; }
- 50% { background-color: #3f3f46; }
-}
-.animate-pulse-bg {
- animation: pulse-bg 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
-}
-
-@keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-.animate-fadeIn {
- animation: fadeIn 0.3s ease-out forwards;
-}
-
-@keyframes slideUp {
- from { opacity: 0; transform: translateY(20px); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-.animate-slideUp {
- animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
-}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
deleted file mode 100644
index 550b6c7..0000000
--- a/frontend/src/app/layout.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Metadata } from "next";
-import "./globals.css";
-
-export const metadata: Metadata = {
- title: "STREMO - Stream Everything",
- description: "A professional streaming platform landing page.",
-};
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
deleted file mode 100644
index 48a291a..0000000
--- a/frontend/src/app/page.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-"use client";
-
-import React, { useState, useMemo } from 'react';
-import { Language } from '@/lib/i18n';
-import { Streamer } from '@/lib/data';
-import { Header } from '@/components/layout/Header';
-import { Sidebar } from '@/components/layout/Sidebar';
-import { HeroBanner } from '@/components/features/home/HeroBanner';
-import { CategoryGrid } from '@/components/features/home/CategoryGrid';
-import { CookieBanner } from '@/components/ui/CookieBanner';
-import { StreamView } from '@/components/features/stream/StreamView';
-import { BrowseView } from '@/components/features/browse/BrowseView';
-import { FollowingView } from '@/components/features/following/FollowingView';
-import { SearchView } from '@/components/features/search/SearchView';
-import { DashboardView } from '@/components/features/dashboard/DashboardView';
-import { Footer } from '@/components/layout/Footer';
-
-type Tab = 'home' | 'browse' | 'following' | 'stream' | 'search' | 'dashboard';
-
-export default function Home() {
- const [activeTab, setActiveTab] = useState('home');
- const [language, setLanguage] = useState('ru'); // Default to Russian
- const [selectedStreamer, setSelectedStreamer] = useState(null);
- const [searchQuery, setSearchQuery] = useState('');
-
- const handleStreamClick = (streamer: Streamer) => {
- setSelectedStreamer(streamer);
- setActiveTab('stream');
- setSearchQuery(''); // Clear search when opening stream
- };
-
- const currentTab = useMemo(() => {
- if (searchQuery.length > 0) return 'search';
- return activeTab;
- }, [searchQuery, activeTab]);
-
- const renderContent = () => {
- switch (currentTab) {
- case 'stream':
- return selectedStreamer ? (
-
- ) : (
- Выберите стрим для просмотра
- );
- case 'browse':
- return ;
- case 'following':
- return ;
- case 'dashboard':
- return ;
- case 'search':
- return ;
- case 'home':
- default:
- return (
-
-
-
-
- );
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
- {renderContent()}
-
- {currentTab !== 'stream' && }
-
-
-
-
-
- );
-}
diff --git a/frontend/src/components/features/browse/BrowseView.tsx b/frontend/src/components/features/browse/BrowseView.tsx
deleted file mode 100644
index d2d490c..0000000
--- a/frontend/src/components/features/browse/BrowseView.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import { BOTTOM_CATEGORIES } from "@/lib/data";
-
-interface BrowseViewProps {
- language: Language;
-}
-
-export function BrowseView({ language }: BrowseViewProps) {
- const t = DICT[language];
-
- return (
-
-
{t.browse}
-
-
-
- Categories
-
-
- Live Channels
-
-
-
-
- {BOTTOM_CATEGORIES.map((category) => (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
- {category.title}
-
-
- {category.viewers}
-
-
- {category.tags.slice(0, 2).map((tag) => (
-
- {tag}
-
- ))}
-
-
- ))}
-
-
- );
-}
diff --git a/frontend/src/components/features/dashboard/DashboardView.tsx b/frontend/src/components/features/dashboard/DashboardView.tsx
deleted file mode 100644
index d7a8ed1..0000000
--- a/frontend/src/components/features/dashboard/DashboardView.tsx
+++ /dev/null
@@ -1,450 +0,0 @@
-"use client";
-
-import React, { useState } from "react";
-import {
- Settings,
- Mic,
- Video,
- Activity,
- MessageSquare,
- Plus,
- Edit,
- Users,
- BarChart,
- Layers,
- Radio,
- Camera,
- Volume2,
- Monitor,
- Zap,
- Shield,
- Image,
- Cpu,
- MoreVertical,
- Send,
- Heart,
- Play,
-} from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-
-interface DashboardViewProps {
- language: Language;
-}
-
-export function DashboardView({ language }: DashboardViewProps) {
- const t = DICT[language];
- const [activeMenu, setActiveMenu] = useState("Settings");
- const [isLive, setIsLive] = useState(false);
-
- return (
-
- {/* Dashboard Sidebar */}
-
-
-
}
- label="Quick Setup"
- active={activeMenu === "Quick Setup"}
- onClick={() => setActiveMenu("Quick Setup")}
- />
-
}
- label="Stream"
- active={activeMenu === "Stream"}
- onClick={() => setActiveMenu("Stream")}
- />
-
-
-
-
}
- label="Scenes & Overlays"
- active={activeMenu === "Scenes"}
- onClick={() => setActiveMenu("Scenes")}
- />
-
}
- label="Live Chat"
- active={activeMenu === "Chat"}
- onClick={() => setActiveMenu("Chat")}
- />
-
}
- label="Guest Management"
- active={activeMenu === "Guests"}
- onClick={() => setActiveMenu("Guests")}
- />
-
}
- label="Media Library"
- active={activeMenu === "Media"}
- onClick={() => setActiveMenu("Media")}
- />
-
}
- label="AI Features"
- active={activeMenu === "AI"}
- onClick={() => setActiveMenu("AI")}
- />
-
}
- label="Settings"
- active={activeMenu === "Settings"}
- onClick={() => setActiveMenu("Settings")}
- />
-
-
-
- {/* Main Content Area */}
-
- {/* Dashboard Header */}
-
-
-
-
- Session
-
-
- {isLive ? "00:06:24" : "00:00:00"}
-
-
-
-
- Viewers
-
-
- {isLive ? "135" : "0"}
-
-
-
-
- Followers
-
-
- 678
-
-
-
-
-
-
-
-
-
-
-
- setIsLive(!isLive)}
- className={`px-6 py-2 rounded-xl font-black text-xs uppercase tracking-widest transition-all active:scale-95 shadow-lg ${isLive ? "bg-red-500 hover:bg-red-600 text-white" : "bg-[#D673A9] hover:bg-[#C25B96] text-black"}`}
- >
- {isLive ? "End Stream" : "Go Live"}
-
-
-
-
- {/* Dynamic Content */}
-
- {isLive ? (
-
- {/* Left: Stream Preview & Actions */}
-
-
-
-
- Stream Preview
-
-
-
-
- Live
-
-
-
-
-
-
-
-
- Live Camera Feed
-
-
- Built-in Camera
-
-
-
-
-
-
-
-
- Quick Actions
-
-
- }
- label="Edit Info"
- />
- }
- label="Manage Poll"
- />
- } label="" />
-
-
-
-
-
-
- {/* Right: Chat */}
-
-
-
- Live Chat
-
-
-
-
-
-
-
-
-
-
-
- ) : (
-
-
{activeMenu}
-
-
- {/* Audio Controls */}
-
-
-
-
- Audio Controls
-
-
-
-
-
-
-
-
- {/* Stream Quality */}
-
-
-
-
- Stream Quality
-
-
-
-
-
- Quality
-
-
- 1080p (Full HD)
-
-
-
-
-
-
-
- {/* Camera Settings */}
-
-
-
-
- Camera Settings
-
-
-
-
-
-
-
-
-
- {/* Stream Preview (Settings) */}
-
-
-
- Stream Preview
-
-
-
-
-
-
- )}
-
-
-
- );
-}
-
-function DashboardMenuItem({ icon, label, active, onClick }: any) {
- return (
-
- {icon}
- {label}
-
- );
-}
-
-function ControlSlider({ label, value }: any) {
- return (
-
-
-
- {label}
-
-
{value}
-
-
-
- );
-}
-
-function QuickActionButton({ icon, label }: any) {
- return (
-
-
- {icon}
-
- {label && (
-
- {label}
-
- )}
-
- );
-}
-
-function ActivityItem({ name, action, time }: any) {
- return (
-
-
-
-
-
-
- {name} {action}
-
-
{time}
-
-
- );
-}
-
-function ChatBubble({ name, text, color }: any) {
- return (
-
-
-
- {name}:
-
- {text}
-
-
- );
-}
diff --git a/frontend/src/components/features/following/FollowingView.tsx b/frontend/src/components/features/following/FollowingView.tsx
deleted file mode 100644
index 0bb642f..0000000
--- a/frontend/src/components/features/following/FollowingView.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import { SIDEBAR_FOLLOWING, Streamer } from "@/lib/data";
-
-interface FollowingViewProps {
- language: Language;
- onStreamClick: (streamer: Streamer) => void;
-}
-
-export function FollowingView({ language, onStreamClick }: FollowingViewProps) {
- const t = DICT[language];
- const liveStreamers = SIDEBAR_FOLLOWING.filter((s) => s.isLive);
- const offlineStreamers = SIDEBAR_FOLLOWING.filter((s) => !s.isLive);
-
- return (
-
-
{t.following}
-
-
-
- Live Channels
-
- Live
-
-
-
- {liveStreamers.map((streamer) => (
- onStreamClick(streamer)}
- />
- ))}
-
-
-
-
-
- Offline Channels
-
-
- {offlineStreamers.map((streamer) => (
-
onStreamClick(streamer)}
- >
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
- {streamer.name}
-
-
- ))}
-
-
-
- );
-}
-
-function StreamCard({ streamer, onClick }: any) {
- return (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
- Live
-
-
- {streamer.viewers} viewers
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
- Chill Stream with {streamer.name}
-
-
{streamer.name}
-
{streamer.game}
-
-
-
- );
-}
diff --git a/frontend/src/components/features/home/CategoryGrid.tsx b/frontend/src/components/features/home/CategoryGrid.tsx
deleted file mode 100644
index 9083a15..0000000
--- a/frontend/src/components/features/home/CategoryGrid.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import { BOTTOM_CATEGORIES } from "@/lib/data";
-
-interface CategoryGridProps {
- language: Language;
-}
-
-export function CategoryGrid({ language }: CategoryGridProps) {
- const t = DICT[language];
-
- return (
- <>
-
-
{t.categoriesLike}
-
- {t.viewAll}
-
-
-
-
- {BOTTOM_CATEGORIES.map((category) => (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
-
- STREMO
-
-
-
-
-
- {category.title}
-
-
- {category.viewers}
-
-
-
- {category.tags.slice(0, 2).map((tag) => (
-
- {tag}
-
- ))}
-
-
- ))}
-
- >
- );
-}
diff --git a/frontend/src/components/features/home/HeroBanner.tsx b/frontend/src/components/features/home/HeroBanner.tsx
deleted file mode 100644
index d932a5c..0000000
--- a/frontend/src/components/features/home/HeroBanner.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-"use client";
-
-import React, { useState } from "react";
-import { Play, Heart, Share } from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-import { RECOMMENDATIONS, Streamer } from "@/lib/data";
-
-interface HeroBannerProps {
- language: Language;
- onStreamClick: (streamer: any) => void;
-}
-
-export function HeroBanner({ language, onStreamClick }: HeroBannerProps) {
- const t = DICT[language];
- const [recIndex, setRecIndex] = useState(0);
-
- const handleWatchNow = () => {
- const currentRec = RECOMMENDATIONS[recIndex];
- onStreamClick({
- id: currentRec.id,
- name: currentRec.channel,
- game: currentRec.game,
- viewers: currentRec.viewers,
- avatar: currentRec.avatar,
- isLive: true,
- title: currentRec.title,
- });
- };
-
- return (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
-
-
-
-
-
- THE WITCHER 3
-
-
- WILD HUNT
-
-
-
-
- {["RPG", "Adventure Game", "Action", "Open World"].map((tag) => (
-
- {tag}
-
- ))}
-
-
-
- The Witcher 3: Wild Hunt
-
-
- Explore a dark-fantasy open world as monster slayer Geralt of Rivia in
- The Witcher 3: Wild Hunt, one of the most acclaimed role-playing games
- of all time.
-
-
-
-
- {t.watchNow}
-
-
-
- {t.follow}
-
-
- {t.share}
-
-
-
-
-
- {/* Recommendation Slider */}
-
-
-
{t.recStreamers}
-
-
- setRecIndex(
- (prev) =>
- (prev - 1 + RECOMMENDATIONS.length) %
- RECOMMENDATIONS.length,
- )
- }
- className="w-5 h-5 flex items-center justify-center text-gray-400 hover:text-white font-medium transition-colors"
- >
- <
-
- {RECOMMENDATIONS.map((_, idx) => (
- setRecIndex(idx)}
- className={`w-1.5 h-1.5 rounded-full transition-all duration-300 mx-0.5 ${idx === recIndex ? "bg-white scale-150" : "bg-gray-500 hover:bg-gray-400"}`}
- />
- ))}
-
- setRecIndex((prev) => (prev + 1) % RECOMMENDATIONS.length)
- }
- className="w-5 h-5 flex items-center justify-center text-gray-400 hover:text-white font-medium transition-colors"
- >
- >
-
-
-
-
- {/* Slider Track */}
-
-
- {RECOMMENDATIONS.map((stream, idx) => {
- const isActive = idx === recIndex;
- return (
-
{
- setRecIndex(idx);
- if (isActive) {
- onStreamClick({
- id: stream.id,
- name: stream.channel,
- game: stream.game,
- viewers: stream.viewers,
- avatar: stream.avatar,
- isLive: true,
- title: stream.title,
- });
- }
- }}
- className={`w-[var(--card-width)] shrink-0 flex flex-col gap-3 transition-opacity duration-500 cursor-pointer ${isActive ? "opacity-100" : "opacity-50 hover:opacity-80"}`}
- >
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
- LIVE
-
-
- {stream.viewers}
-
-
-
-
-
- {stream.title}
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
- {stream.channel}
-
-
- {stream.game}
-
-
-
-
-
- );
- })}
-
-
-
-
- );
-}
diff --git a/frontend/src/components/features/search/SearchView.tsx b/frontend/src/components/features/search/SearchView.tsx
deleted file mode 100644
index 5fb001f..0000000
--- a/frontend/src/components/features/search/SearchView.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import {
- BOTTOM_CATEGORIES,
- SIDEBAR_FOLLOWING,
- SIDEBAR_POPULAR,
- Streamer,
-} from "@/lib/data";
-import { User, Hash, PlayCircle } from "lucide-react";
-
-interface SearchViewProps {
- query: string;
- language: Language;
- onStreamClick: (streamer: Streamer) => void;
-}
-
-export function SearchView({
- query,
- language,
- onStreamClick,
-}: SearchViewProps) {
- const t = DICT[language];
- const lowerQuery = query.toLowerCase();
-
- const filteredCategories = BOTTOM_CATEGORIES.filter(
- (c) =>
- c.title.toLowerCase().includes(lowerQuery) ||
- c.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)),
- );
-
- const allStreamers = [...SIDEBAR_FOLLOWING, ...SIDEBAR_POPULAR];
- const filteredStreamers = allStreamers.filter(
- (s, index, self) =>
- self.findIndex((t) => t.id === s.id) === index &&
- (s.name.toLowerCase().includes(lowerQuery) ||
- s.game.toLowerCase().includes(lowerQuery)),
- );
-
- return (
-
-
-
-
-
- {language === "ru" ? "Результаты поиска" : "Search Results"}
-
-
- {language === "ru" ? "Для запроса" : "For query"}:{" "}
- "{query}"
-
-
-
-
- {filteredStreamers.length > 0 && (
-
-
-
-
- {language === "ru" ? "Каналы" : "Channels"}
-
-
-
- {filteredStreamers.map((streamer) => (
-
onStreamClick(streamer)}
- >
-
-
-
-
-
-
- {streamer.name}
-
-
- {streamer.game || "Offline"}
-
-
- {streamer.viewers}{" "}
- {language === "ru" ? "зрителей" : "viewers"}
-
-
-
- ))}
-
-
- )}
-
- {filteredCategories.length > 0 && (
-
-
-
-
- {language === "ru" ? "Категории" : "Categories"}
-
-
-
- {filteredCategories.map((category) => (
-
-
-
-
-
-
- {category.title}
-
-
- {category.viewers}
-
-
- {category.tags.slice(0, 2).map((tag) => (
-
- {tag}
-
- ))}
-
-
- ))}
-
-
- )}
-
- {filteredStreamers.length === 0 && filteredCategories.length === 0 && (
-
-
-
-
-
- No matches found for "{query}"
-
-
- Try different keywords or filters
-
-
- )}
-
- );
-}
diff --git a/frontend/src/components/features/stream/StreamView.tsx b/frontend/src/components/features/stream/StreamView.tsx
deleted file mode 100644
index ad74913..0000000
--- a/frontend/src/components/features/stream/StreamView.tsx
+++ /dev/null
@@ -1,431 +0,0 @@
-"use client";
-
-import React, { useState, useEffect, useRef } from "react";
-import {
- Heart,
- Share,
- Users,
- MessageSquare,
- Send,
- Smile,
- Gift,
- Trophy,
- ExternalLink,
- Camera,
- Bird,
- Code,
-} from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-import { Streamer } from "@/lib/data";
-
-interface StreamViewProps {
- streamer: Streamer;
- language: Language;
-}
-
-interface ChatMsg {
- id: number;
- user: string;
- text: string;
- color: string;
- isMod?: boolean;
- isVip?: boolean;
-}
-
-const STICKERS = ["🔥", "😂", "😮", "❤️", "👍", "🤡", "👑", "GG"];
-
-export function StreamView({ streamer, language }: StreamViewProps) {
- const t = DICT[language];
- const [messages, setMessages] = useState([
- {
- id: 1,
- user: "Alex_Pro",
- text: "PogChamp! What a play!",
- color: "#D673A9",
- },
- { id: 2, user: "KillerBee", text: "Is this real life?", color: "#60a5fa" },
- {
- id: 3,
- user: "StreamMod",
- text: "Welcome everyone! Please be respectful.",
- color: "#4ade80",
- isMod: true,
- },
- {
- id: 4,
- user: "ZeldaFan",
- text: "Anyone knows the song name?",
- color: "#fbbf24",
- isVip: true,
- },
- ]);
- const [inputText, setInputText] = useState("");
- const [showStickers, setShowStickers] = useState(false);
- const [bonusPoints, setBonusPoints] = useState(1250);
- const chatEndRef = useRef(null);
-
- const scrollToBottom = () => {
- chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
- };
-
- useEffect(scrollToBottom, [messages]);
-
- const handleSendMessage = (e?: React.FormEvent) => {
- e?.preventDefault();
- if (!inputText.trim()) return;
-
- const newMsg: ChatMsg = {
- id: Date.now(),
- user: "Dmitry_X",
- text: inputText,
- color: "#D673A9",
- };
-
- setMessages([...messages, newMsg]);
- setInputText("");
- setBonusPoints((prev) => prev + 10);
- };
-
- const addSticker = (sticker: string) => {
- setInputText((prev) => prev + " " + sticker);
- setShowStickers(false);
- };
-
- return (
-
-
- {/* Player Area */}
-
-
-
-
-
-
- STREMO PLAYER
-
-
-
-
-
-
-
-
-
-
-
- {/* Channel Info */}
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
- {streamer.title || `${streamer.name}'s Chill Stream`}
-
-
-
- {streamer.name}
-
-
- • {streamer.game}
-
-
-
-
-
-
-
-
- {streamer.viewers}
-
-
- {" "}
- {t.follow}
-
-
-
-
-
-
-
- {/* About & Banners Section */}
-
-
-
-
- {t.about}
-
- {/*Replace on real ddtd in rq #2-#3*/}
-
- Welcome to my channel! I'm {streamer.name}, a professional{" "}
- {streamer.game} player. Don't forget to follow and join our
- discord for giveaways and community games! Business:
- contact@{streamer.name.toLowerCase()}.com
-
-
-
- 1.2M
-
- {t.followers}
-
-
-
-
- 8.5k
-
- Subs
-
-
-
-
-
-
-
-
-
-
-
-
- Merch Store
-
-
- 20% OFF NOW
-
-
-
-
-
-
-
-
-
- Instagram
-
-
- @official_{streamer.name.toLowerCase()}
-
-
-
-
-
-
-
-
-
- {t.links}
-
-
- }
- label="Twitter"
- url="#"
- />
- }
- label="Instagram"
- url="#"
- />
- }
- label="Discord"
- url="#"
- />
-
-
-
-
-
- STREMO PLUS
-
-
- Subscribe for exclusive emotes and ad-free viewing!
-
-
- Upgrade
-
-
-
-
-
-
-
- {/* Chat Area */}
-
-
-
- );
-}
-
-function SocialLink({ icon, label, url }: any) {
- return (
-
-
-
- {icon}
-
- {label}
-
-
-
- );
-}
-
-function ChatMessage({ user, text, color, isMod, isVip }: any) {
- return (
-
-
- {isMod && (
-
-
-
- )}
- {isVip && (
-
-
-
- )}
-
- {user}
-
-
{text}
-
-
- );
-}
diff --git a/frontend/src/components/layout/Footer.tsx b/frontend/src/components/layout/Footer.tsx
deleted file mode 100644
index f473d0b..0000000
--- a/frontend/src/components/layout/Footer.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import { Bird, Camera, Code, Play } from "lucide-react";
-
-interface FooterProps {
- language: Language;
-}
-
-export function Footer({ language }: FooterProps) {
- const t = DICT[language];
-
- return (
-
- );
-}
-
-function SocialIcon({ icon }: any) {
- return (
-
- {icon}
-
- );
-}
diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx
deleted file mode 100644
index b9cc444..0000000
--- a/frontend/src/components/layout/Header.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-"use client";
-
-import React, { useState } from "react";
-import {
- Home,
- Copy,
- User,
- Search,
- Bell,
- MessageCircle,
- Settings,
- Globe,
- LogOut,
- ChevronLeft,
- ChevronRight,
- Check,
-} from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-import { SearchOverlay } from "./SearchOverlay";
-import { Streamer } from "@/lib/data";
-
-interface HeaderProps {
- activeTab: string;
- setActiveTab: (tab: string) => void;
- language: Language;
- setLanguage: (lang: Language) => void;
- searchQuery: string;
- setSearchQuery: (query: string) => void;
- onStreamClick: (streamer: Streamer) => void;
-}
-
-export function Header({
- activeTab,
- setActiveTab,
- language,
- setLanguage,
- searchQuery,
- setSearchQuery,
- onStreamClick,
-}: HeaderProps) {
- const [isProfileOpen, setIsProfileOpen] = useState(false);
- const [profileView, setProfileView] = useState<"main" | "lang">("main");
- const [isSearchOpen, setIsSearchOpen] = useState(false);
- const t = DICT[language];
-
- React.useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === "/" && !isSearchOpen) {
- e.preventDefault();
- setIsSearchOpen(true);
- }
- };
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [isSearchOpen]);
-
- return (
-
-
-
setActiveTab("home")}
- >
-
-
-
-
- STREMO
-
-
-
-
- setActiveTab("home")}
- className={`flex items-center gap-2 h-10 px-4 rounded-full font-bold text-sm transition-all ${activeTab === "home" ? "bg-white text-black" : "text-white hover:bg-white/10"}`}
- >
- {" "}
- {t.home}
-
- setActiveTab("browse")}
- className={`flex items-center gap-2 h-10 px-4 rounded-full font-bold text-sm transition-all ${activeTab === "browse" ? "bg-white text-black" : "text-white hover:bg-white/10"}`}
- >
- {t.browse}
-
- setActiveTab("following")}
- className={`flex items-center gap-2 h-10 px-4 rounded-full font-bold text-sm transition-all ${activeTab === "following" ? "bg-white text-black" : "text-white hover:bg-white/10"}`}
- >
- {t.following}
-
-
-
-
-
-
setIsSearchOpen(true)}
- className="flex items-center w-full h-10 px-4 rounded-full bg-[#27272a]/60 border border-transparent hover:border-[#D673A9]/30 transition-all cursor-text group"
- >
-
-
- {searchQuery || t.search}
-
-
- /
-
-
-
-
- setIsSearchOpen(false)}
- query={searchQuery}
- setQuery={setSearchQuery}
- language={language}
- onStreamClick={(s) => {
- onStreamClick(s);
- setIsSearchOpen(false);
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
setActiveTab("dashboard")}
- className="hidden sm:flex items-center gap-2 h-9 px-4 rounded-full bg-[#D673A9] hover:bg-[#C25B96] text-black font-bold text-sm transition-transform active:scale-95"
- >
-
{t.goLive}
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
{
- setIsProfileOpen(!isProfileOpen);
- setProfileView("main");
- }}
- className={`w-9 h-9 rounded-full cursor-pointer bg-white border-2 transition-colors ${isProfileOpen ? "border-[#D673A9]" : "border-transparent hover:border-[#D673A9]/50"}`}
- />
-
- {isProfileOpen && (
- <>
-
setIsProfileOpen(false)}
- >
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-
-
-
-
- {profileView === "main" ? (
-
-
}
- label={t.profile}
- />
-
}
- label={t.settings}
- />
-
}
- label={t.language}
- value={language === "ru" ? "Русский" : "English"}
- onClick={() => setProfileView("lang")}
- showArrow
- />
-
-
}
- label={t.logout}
- hoverClass="hover:bg-red-500/10 hover:text-red-500"
- />
-
- ) : (
-
- setProfileView("main")}
- className="flex items-center gap-2 px-3 py-2.5 text-sm font-bold text-[#a1a1aa] hover:text-white rounded-xl hover:bg-white/5 mb-1 transition-colors"
- >
- {t.back}
-
- {
- setLanguage(lang as Language);
- setProfileView("main");
- }}
- />
- {
- setLanguage(lang as Language);
- setProfileView("main");
- }}
- />
-
- )}
-
- >
- )}
-
-
-
- );
-}
-
-function ProfileMenuItem({
- icon,
- label,
- value,
- onClick,
- showArrow,
- hoverClass = "hover:bg-white/5 hover:text-white",
-}: any) {
- return (
-
-
- {icon}
- {label}
-
-
- {value && (
-
- {value}
-
- )}
- {showArrow && }
-
-
- );
-}
-
-function LanguageOption({ label, code, active, onSelect }: any) {
- return (
- onSelect(code)}
- className="flex items-center justify-between px-3 py-2.5 rounded-xl cursor-pointer text-[#a1a1aa] hover:bg-white/5 hover:text-white transition-colors"
- >
-
- {label}
-
- {active && }
-
- );
-}
diff --git a/frontend/src/components/layout/SearchOverlay.tsx b/frontend/src/components/layout/SearchOverlay.tsx
deleted file mode 100644
index 6915846..0000000
--- a/frontend/src/components/layout/SearchOverlay.tsx
+++ /dev/null
@@ -1,369 +0,0 @@
-"use client";
-
-import React, { useEffect, useRef } from "react";
-import {
- Search,
- X,
- TrendingUp,
- User,
- Play,
- Hash,
- Clock,
- ArrowRight,
-} from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-import {
- BOTTOM_CATEGORIES,
- SIDEBAR_FOLLOWING,
- SIDEBAR_POPULAR,
- Streamer,
-} from "@/lib/data";
-
-interface SearchOverlayProps {
- isOpen: boolean;
- onClose: () => void;
- query: string;
- setQuery: (query: string) => void;
- language: Language;
- onStreamClick: (streamer: Streamer) => void;
-}
-
-export function SearchOverlay({
- isOpen,
- onClose,
- query,
- setQuery,
- language,
- onStreamClick,
-}: SearchOverlayProps) {
- const t = DICT[language];
- const inputRef = useRef(null);
-
- useEffect(() => {
- if (isOpen) {
- document.body.style.overflow = "hidden";
- setTimeout(() => inputRef.current?.focus(), 100);
- } else {
- document.body.style.overflow = "auto";
- }
- }, [isOpen]);
-
- useEffect(() => {
- const handleEsc = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- window.addEventListener("keydown", handleEsc);
- return () => window.removeEventListener("keydown", handleEsc);
- }, [onClose]);
-
- if (!isOpen) return null;
-
- const lowerQuery = query.toLowerCase();
-
- const allStreamers = [...SIDEBAR_FOLLOWING, ...SIDEBAR_POPULAR];
- const filteredStreamers = allStreamers
- .filter(
- (s, index, self) =>
- self.findIndex((t) => t.id === s.id) === index && // Unique by id
- (s.name.toLowerCase().includes(lowerQuery) ||
- s.game.toLowerCase().includes(lowerQuery)),
- )
- .slice(0, 6);
-
- const filteredCategories = BOTTOM_CATEGORIES.filter(
- (c) =>
- c.title.toLowerCase().includes(lowerQuery) ||
- c.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)),
- ).slice(0, 8);
-
- const trendingCategories = BOTTOM_CATEGORIES.slice(0, 4);
- const recentSearches = [
- language === "ru" ? "Киберспорт" : "Esports",
- "Minecraft",
- "ASMR",
- language === "ru" ? "Разговоры" : "Just Chatting",
- ];
-
- return (
-
- {/* Search Header */}
-
-
-
-
-
-
- setQuery(e.target.value)}
- placeholder={t.search}
- className="w-full bg-transparent outline-none text-xl font-bold text-white placeholder-[#55555a]"
- />
- {query && (
- setQuery("")}
- className="p-1.5 hover:bg-white/10 rounded-full transition-colors"
- >
-
-
- )}
-
-
-
-
-
-
-
-
- {/* Results / Suggestions */}
-
-
- {!query ? (
-
- {/* Left Column: Recent & Trending */}
-
-
-
- {" "}
- {language === "ru" ? "Недавние запросы" : "Recent Searches"}
-
-
- {recentSearches.map((search) => (
-
setQuery(search)}
- className="flex items-center justify-between p-3 rounded-xl hover:bg-white/5 text-left group transition-colors"
- >
-
- {search}
-
-
-
- ))}
-
-
-
-
-
- {" "}
- {language === "ru" ? "Популярно сейчас" : "Trending Now"}
-
-
- {[
- "Gaming",
- "ASMR",
- "Programming",
- "Art",
- "Music",
- "Chatting",
- ].map((tag) => (
- setQuery(tag)}
- className="px-4 py-2 rounded-full bg-[#18181b] border border-white/5 hover:border-[#D673A9]/50 hover:bg-[#D673A9]/5 text-sm font-bold text-[#a1a1aa] hover:text-[#D673A9] transition-all"
- >
- {tag}
-
- ))}
-
-
-
-
- {/* Right Column: Suggested Content */}
-
-
-
-
- {language === "ru"
- ? "Популярные категории"
- : "Top Categories"}
-
- setQuery(" ")}
- >
- View All
-
-
-
- {trendingCategories.map((cat) => (
-
setQuery(cat.title)}
- >
-
-
-
-
- {cat.viewers.split(" ")[0]}
-
-
-
- {cat.title}
-
-
- {cat.tags[0]}
-
-
- ))}
-
-
-
-
-
- {language === "ru"
- ? "Рекомендуемые каналы"
- : "Recommended Channels"}
-
-
- {SIDEBAR_POPULAR.slice(0, 4).map((streamer) => (
-
onStreamClick(streamer)}
- >
-
-
-
- Live
-
-
-
-
- {streamer.name}
-
-
- {streamer.game}
-
-
- {streamer.viewers}{" "}
- {language === "ru" ? "зрителей" : "viewers"}
-
-
-
- ))}
-
-
-
-
- ) : (
-
- {filteredStreamers.length > 0 && (
-
-
- Channels
-
-
- {filteredStreamers.map((s) => (
-
onStreamClick(s)}
- >
-
-
-
- {s.name}
-
-
- {s.game}
-
-
-
-
- ))}
-
-
- )}
-
- {filteredCategories.length > 0 && (
-
-
- Categories
-
-
- {filteredCategories.map((cat) => (
-
setQuery(cat.title)}
- >
-
-
-
-
- {cat.title}
-
-
- {cat.viewers.split(" ")[0]}
-
-
- ))}
-
-
- )}
-
- {filteredStreamers.length === 0 &&
- filteredCategories.length === 0 && (
-
-
-
-
-
- No results for "{query}"
-
-
- Try checking your spelling or use more general keywords.
-
-
- )}
-
- )}
-
-
-
- );
-}
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx
deleted file mode 100644
index 2ea6728..0000000
--- a/frontend/src/components/layout/Sidebar.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-"use client";
-
-import React from "react";
-import { DICT, Language } from "@/lib/i18n";
-import {
- SIDEBAR_FOLLOWING,
- SIDEBAR_POPULAR,
- SIDEBAR_CATEGORIES,
- Streamer,
-} from "@/lib/data";
-import { Clock } from "lucide-react";
-
-interface SidebarProps {
- language: Language;
- onStreamClick: (streamer: Streamer) => void;
-}
-
-export function Sidebar({ language, onStreamClick }: SidebarProps) {
- const t = DICT[language];
-
- return (
-
-
-
-
-
-
-
-
- History
-
-
- {SIDEBAR_FOLLOWING.slice(0, 3).map((item) => (
-
onStreamClick(item)}
- >
-
-
-
- {item.name}
-
-
- Last seen 2d ago
-
-
-
- ))}
-
-
-
-
- );
-}
-
-function SidebarSection({ title, items, btnText, onClick, type }: any) {
- return (
-
-
- {title}
-
-
- {items.map((item: any) => (
-
(onClick ? onClick(item) : null)}
- >
-
-
- {type === "streamer" && item.isLive && (
-
- )}
-
-
-
-
- {type === "streamer" ? item.name : item.title}
-
-
- {item.isLive && (
-
- )}
-
- {item.viewers}
-
-
-
-
- {type === "streamer" ? item.game || "Offline" : item.desc}
-
-
-
- ))}
-
-
- {btnText}
-
-
- );
-}
diff --git a/frontend/src/components/ui/CookieBanner.tsx b/frontend/src/components/ui/CookieBanner.tsx
deleted file mode 100644
index 08a9467..0000000
--- a/frontend/src/components/ui/CookieBanner.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-"use client";
-
-import React, { useState } from "react";
-import { Cookie } from "lucide-react";
-import { DICT, Language } from "@/lib/i18n";
-
-interface CookieBannerProps {
- language: Language;
-}
-
-export function CookieBanner({ language }: CookieBannerProps) {
- const [showCookieBanner, setShowCookieBanner] = useState(true);
- const [isCookieClosing, setIsCookieClosing] = useState(false);
-
- const t = DICT[language];
-
- const handleCloseCookie = () => {
- setIsCookieClosing(true);
- setTimeout(() => setShowCookieBanner(false), 400);
- };
-
- if (!showCookieBanner) return null;
-
- return (
-
-
-
-
-
-
- {t.cookieText}
-
-
-
-
- {t.decline}
-
-
- {t.accept}
-
-
-
- );
-}
diff --git a/frontend/src/lib/data.ts b/frontend/src/lib/data.ts
deleted file mode 100644
index c7a6cac..0000000
--- a/frontend/src/lib/data.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-export interface Streamer {
- id: number;
- name: string;
- game: string;
- viewers: string;
- avatar: string;
- isLive: boolean;
- title?: string;
- tags?: string[];
-}
-
-export interface Category {
- id: number;
- title: string;
- viewers: string;
- tags: string[];
- image: string;
- desc?: string;
-}
-
-export const SIDEBAR_FOLLOWING: Streamer[] = [
- {
- id: 1,
- name: "Clemovitch",
- game: "Just Chatting",
- viewers: "17.3K",
- avatar: "https://i.pravatar.cc/150?u=1",
- isLive: true,
- },
- {
- id: 2,
- name: "Glorious_E",
- game: "The Witcher 3: Wild Hunt",
- viewers: "56.5K",
- avatar: "https://i.pravatar.cc/150?u=2",
- isLive: true,
- },
- {
- id: 3,
- name: "Camy",
- game: "Call of Duty: Warzone",
- viewers: "1.3K",
- avatar: "https://i.pravatar.cc/150?u=3",
- isLive: true,
- },
- {
- id: 4,
- name: "BLASTPremier",
- game: "",
- viewers: "Offline",
- avatar: "https://i.pravatar.cc/150?u=4",
- isLive: false,
- },
- {
- id: 5,
- name: "fubgun",
- game: "",
- viewers: "Offline",
- avatar: "https://i.pravatar.cc/150?u=5",
- isLive: false,
- },
-];
-
-export const SIDEBAR_POPULAR: Streamer[] = [
- {
- id: 6,
- name: "KaiCenat",
- game: "Just Chatting",
- viewers: "56.5K",
- avatar: "https://i.pravatar.cc/150?u=6",
- isLive: true,
- },
- {
- id: 7,
- name: "Jynxzi",
- game: "I'm Only Sleeping",
- viewers: "9.3K",
- avatar: "https://i.pravatar.cc/150?u=7",
- isLive: true,
- },
- {
- id: 8,
- name: "IShowSpeed",
- game: "IRL",
- viewers: "12.4K",
- avatar: "https://i.pravatar.cc/150?u=8",
- isLive: true,
- },
- {
- id: 9,
- name: "Caedrel",
- game: "Hollow Knight",
- viewers: "18.6K",
- avatar: "https://i.pravatar.cc/150?u=9",
- isLive: true,
- },
- {
- id: 10,
- name: "faxuty",
- game: "Fortnite",
- viewers: "9.9K",
- avatar: "https://i.pravatar.cc/150?u=10",
- isLive: true,
- },
-];
-
-export const SIDEBAR_CATEGORIES: Category[] = [
- {
- id: 1,
- title: "Politics",
- desc: "IRL",
- viewers: "56.5K",
- image: "https://picsum.photos/seed/pol/100/100",
- tags: ["IRL"],
- },
- {
- id: 2,
- title: "Just Chatting",
- desc: "IRL",
- viewers: "56.5K",
- image: "https://picsum.photos/seed/chat/100/100",
- tags: ["IRL"],
- },
- {
- id: 3,
- title: "Counter-Strike",
- desc: "FPS, Shooter",
- viewers: "56.5K",
- image: "https://picsum.photos/seed/csgo/100/100",
- tags: ["FPS", "Shooter"],
- },
- {
- id: 4,
- title: "Path of Exile 2",
- desc: "RPG, Action",
- viewers: "56.5K",
- image: "https://picsum.photos/seed/poe/100/100",
- tags: ["RPG", "Action"],
- },
- {
- id: 5,
- title: "The Witcher 3: W...",
- desc: "RPG",
- viewers: "56.5K",
- image: "https://picsum.photos/seed/witcher_icon/100/100",
- tags: ["RPG"],
- },
-];
-
-export const RECOMMENDATIONS = [
- {
- id: 1,
- title: "Direction Kear Morhen | Marathon THE WITCHER 3 + DL...",
- channel: "Glorious_E",
- game: "The Witcher 3: Wild Hunt",
- viewers: "56.5K viewers",
- image: "https://picsum.photos/seed/witcher_stream/600/338",
- avatar: "https://i.pravatar.cc/150?u=2",
- },
- {
- id: 2,
- title: "⭐#1 RANKED POV⭐30% OFF AL...",
- channel: "Camy",
- game: "Call of Duty: Warzone",
- viewers: "1.3K viewers",
- image: "https://picsum.photos/seed/cod/600/338",
- avatar: "https://i.pravatar.cc/150?u=3",
- },
- {
- id: 3,
- title: "ROAD TO GLOBAL ELITE | CS2 CHILL STREAM",
- channel: "s1mple_fan",
- game: "Counter-Strike 2",
- viewers: "24.1K viewers",
- image: "https://picsum.photos/seed/cs2_stream/600/338",
- avatar: "https://i.pravatar.cc/150?u=4",
- },
- {
- id: 4,
- title: "Just chatting and reacting to new trailers",
- channel: "Pokimane_fan",
- game: "Just Chatting",
- viewers: "32.8K viewers",
- image: "https://picsum.photos/seed/chat_stream/600/338",
- avatar: "https://i.pravatar.cc/150?u=5",
- },
-];
-
-export const BOTTOM_CATEGORIES: Category[] = [
- {
- id: 1,
- title: "Just Chatting",
- viewers: "232K viewers",
- tags: ["IRL", "Casual"],
- image: "https://picsum.photos/seed/justchat/400/533",
- },
- {
- id: 2,
- title: "IRL",
- viewers: "142K viewers",
- tags: ["IRL", "Adventure"],
- image: "https://picsum.photos/seed/irl_cat/400/533",
- },
- {
- id: 3,
- title: "Minecraft",
- viewers: "67.9K viewers",
- tags: ["Simulation"],
- image: "https://picsum.photos/seed/mine/400/533",
- },
- {
- id: 4,
- title: "Dota 2",
- viewers: "59.7K viewers",
- tags: ["Strategy", "MOBA"],
- image: "https://picsum.photos/seed/dota2/400/533",
- },
- {
- id: 5,
- title: "Counter-Strike",
- viewers: "21.2K viewers",
- tags: ["FPS", "Shooter"],
- image: "https://picsum.photos/seed/cs2/400/533",
- },
- {
- id: 6,
- title: "Path of Exile 2",
- viewers: "128K viewers",
- tags: ["Action", "RPG"],
- image: "https://picsum.photos/seed/poe2/400/533",
- },
- {
- id: 7,
- title: "Politics",
- viewers: "45.1K viewers",
- tags: ["IRL"],
- image: "https://picsum.photos/seed/polcat/400/533",
- },
- {
- id: 8,
- title: "The Witcher 3",
- viewers: "32.4K viewers",
- tags: ["RPG"],
- image: "https://picsum.photos/seed/w3cat/400/533",
- },
-];
diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts
deleted file mode 100644
index e849a34..0000000
--- a/frontend/src/lib/i18n.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-export type Language = "en" | "ru";
-
-export const DICT = {
- en: {
- home: "Home",
- browse: "Browse",
- following: "Following",
- search: "Search",
- goLive: "Go Live",
- cookieText:
- "We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking 'Accept', you consent to our use of cookies.",
- accept: "Accept",
- decline: "Decline",
- recStreamers: "Recommendation Streamers",
- categoriesLike: "Categories we think you'll like",
- viewAll: "View All",
- watchNow: "Watch Now",
- follow: "Follow",
- share: "Share",
- sidebarFollowing: "Following",
- sidebarPopular: "Popular",
- sidebarCategories: "Categories",
- showMore: "Show More",
- profile: "Profile",
- settings: "Settings",
- language: "Language",
- logout: "Log Out",
- back: "Back",
- online: "Online",
- about: "About",
- followers: "Followers",
- chatBonus: "Bonus",
- send: "Send",
- links: "Social Links",
- partners: "Partners",
- copyright: "© 2026 STREMO. All rights reserved.",
- description:
- "The best streaming platform for creators and viewers alike. Join the community and start your journey today.",
- },
- ru: {
- home: "Главная",
- browse: "Обзор",
- following: "Подписки",
- search: "Поиск",
- goLive: "В эфир",
- cookieText:
- "Мы используем файлы cookie для персонализации контента и улучшения пользовательского опыта. Нажимая «Принять», вы соглашаетесь с нашей политикой.",
- accept: "Принять",
- decline: "Отклонить",
- recStreamers: "Рекомендуемые стримеры",
- categoriesLike: "Категории для вас",
- viewAll: "Все",
- watchNow: "Смотреть",
- follow: "Отслеживать",
- share: "Поделиться",
- sidebarFollowing: "Отслеживаемые",
- sidebarPopular: "Популярное",
- sidebarCategories: "Категории",
- showMore: "Показать еще",
- profile: "Мой канал",
- settings: "Настройки",
- language: "Язык",
- logout: "Выйти",
- back: "Назад",
- online: "В сети",
- about: "О канале",
- followers: "Подписчиков",
- chatBonus: "Бонус",
- send: "Отправить",
- links: "Соцсети",
- partners: "Партнеры",
- copyright: "© 2026 STREMO. Все права защищены.",
- description:
- "Лучшая стриминговая платформа для создателей и зрителей. Присоединяйтесь к сообществу и начните свой путь сегодня.",
- },
-};
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
deleted file mode 100644
index 151a220..0000000
--- a/frontend/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2017",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "react-jsx",
- "incremental": true,
- "plugins": [
- {
- "name": "next",
- },
- ],
- "paths": {
- "@/*": ["./src/*"],
- },
- },
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- ".next/dev/types/**/*.ts",
- "**/*.mts",
- ],
- "exclude": ["node_modules"],
-}
diff --git a/img/Screenshot 2026-05-07 at 19.33.02.png b/img/Screenshot 2026-05-07 at 19.33.02.png
deleted file mode 100644
index 7d2321e..0000000
Binary files a/img/Screenshot 2026-05-07 at 19.33.02.png and /dev/null differ
diff --git a/img/api-md.png b/img/api-md.png
deleted file mode 100644
index f74c274..0000000
Binary files a/img/api-md.png and /dev/null differ
diff --git a/img/arch-md.png b/img/arch-md.png
deleted file mode 100644
index 3f6c6ae..0000000
Binary files a/img/arch-md.png and /dev/null differ
diff --git a/img/ci-cd-md.png b/img/ci-cd-md.png
deleted file mode 100644
index 00f5b3c..0000000
Binary files a/img/ci-cd-md.png and /dev/null differ
diff --git a/img/db-md.png b/img/db-md.png
deleted file mode 100644
index e850fa1..0000000
Binary files a/img/db-md.png and /dev/null differ
diff --git a/img/deploy-md.png b/img/deploy-md.png
deleted file mode 100644
index 2965d1d..0000000
Binary files a/img/deploy-md.png and /dev/null differ
diff --git a/img/image copy.png b/img/image copy.png
deleted file mode 100644
index 732b3f2..0000000
Binary files a/img/image copy.png and /dev/null differ
diff --git a/img/image.png b/img/image.png
deleted file mode 100644
index e1387c7..0000000
Binary files a/img/image.png and /dev/null differ
diff --git a/img/sharding-md.png b/img/sharding-md.png
deleted file mode 100644
index d91a844..0000000
Binary files a/img/sharding-md.png and /dev/null differ
diff --git a/img/title-readme.png b/img/title-readme.png
deleted file mode 100644
index 8ef5651..0000000
Binary files a/img/title-readme.png and /dev/null differ
diff --git a/img/user-md.png b/img/user-md.png
deleted file mode 100644
index 631cd93..0000000
Binary files a/img/user-md.png and /dev/null differ
diff --git a/infra/apps/base/bff/deployment.yaml b/infra/apps/base/bff/deployment.yaml
deleted file mode 100644
index a9f734a..0000000
--- a/infra/apps/base/bff/deployment.yaml
+++ /dev/null
@@ -1,51 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: bff-config
- namespace: apps
-data:
- NODE_ENV: "production"
- PORT: "3000"
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: bff
- namespace: apps
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: bff
- template:
- metadata:
- labels:
- app: bff
- spec:
- containers:
- - name: bff
- image: nginx:alpine
- ports:
- - containerPort: 3000
- envFrom:
- - configMapRef:
- name: bff-config
- resources:
- requests:
- memory: "64Mi"
- cpu: "100m"
- limits:
- memory: "128Mi"
- cpu: "250m"
----
-apiVersion: v1
-kind: Service
-metadata:
- name: bff-service
- namespace: apps
-spec:
- ports:
- - port: 3000
- targetPort: 3000
- selector:
- app: bff
diff --git a/infra/apps/base/ingest/deployment.yaml b/infra/apps/base/ingest/deployment.yaml
deleted file mode 100644
index 2b0880e..0000000
--- a/infra/apps/base/ingest/deployment.yaml
+++ /dev/null
@@ -1,55 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: ingest-config
- namespace: apps
-data:
- RTMP_PORT: "1935"
- REDIS_HOST: "redis.data-layer.svc.cluster.local"
- KAFKA_BROKERS: "redpanda.data-layer.svc.cluster.local:9092"
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: ingest
- namespace: apps
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: ingest
- template:
- metadata:
- labels:
- app: ingest
- spec:
- containers:
- - name: ingest
- image: debian:bullseye-slim # потом поправить как нить
- command: ["sleep", "infinity"]
- ports:
- - containerPort: 1935
- envFrom:
- - configMapRef:
- name: ingest-config
- resources:
- requests:
- memory: "64Mi"
- cpu: "100m"
- limits:
- memory: "128Mi"
- cpu: "500m"
----
-apiVersion: v1
-kind: Service
-metadata:
- name: ingest-service
- namespace: apps
-spec:
- type: LoadBalancer
- ports:
- - port: 1935
- targetPort: 1935
- protocol: TCP
- selector:
- app: ingest
diff --git a/infra/apps/base/ingress/traefik-ingress.yaml b/infra/apps/base/ingress/traefik-ingress.yaml
deleted file mode 100644
index 981d96f..0000000
--- a/infra/apps/base/ingress/traefik-ingress.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-apiVersion: traefik.containo.us/v1alpha1
-kind: IngressRoute
-metadata:
- name: api-ingress
- namespace: apps
-spec:
- entryPoints:
- - web
- routes:
- - match: PathPrefix(`/v1`) || PathPrefix(`/api`)
- kind: Rule
- services:
- - name: bff-service
- port: 3000
- - match: PathPrefix(`/ws`)
- kind: Rule
- services:
- - name: chat-service
- port: 8080
----
-apiVersion: traefik.containo.us/v1alpha1
-kind: IngressRoute
-metadata:
- name: monitoring-ingress
- namespace: monitoring
-spec:
- entryPoints:
- - web
- routes:
- - match: Host(`grafana.localhost`)
- kind: Rule
- services:
- - name: grafana
- port: 80
- - match: Host(`prometheus.localhost`)
- kind: Rule
- services:
- - name: prometheus
- port: 9090
diff --git a/infra/apps/base/kustomization.yaml b/infra/apps/base/kustomization.yaml
deleted file mode 100644
index 9687d03..0000000
--- a/infra/apps/base/kustomization.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ingress/traefik-ingress.yaml
- - bff/deployment.yaml
- - ingest/deployment.yaml
diff --git a/infra/apps/overlays/dev/kustomization.yaml b/infra/apps/overlays/dev/kustomization.yaml
deleted file mode 100644
index e47042c..0000000
--- a/infra/apps/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
-patches:
- - target:
- kind: Deployment
- name: bff
- patch: |-
- - op: replace
- path: /spec/template/spec/containers/0/envFrom/0/configMapRef/name
- value: dev-bff-config
- - target:
- kind: ConfigMap
- name: bff-config
- patch: |-
- - op: replace
- path: /data/NODE_ENV
- value: "development"
diff --git a/infra/apps/overlays/prod/kustomization.yaml b/infra/apps/overlays/prod/kustomization.yaml
deleted file mode 100644
index 9713d7d..0000000
--- a/infra/apps/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-patches:
- - target:
- kind: Deployment
- name: bff
- patch: |-
- - op: replace
- path: /spec/replicas
- value: 5
- - op: replace
- path: /spec/template/spec/containers/0/resources/limits/memory
- value: "1Gi"
- - op: replace
- path: /spec/template/spec/containers/0/resources/limits/cpu
- value: "1000m"
diff --git a/infra/apps/overlays/stage/kustomization.yaml b/infra/apps/overlays/stage/kustomization.yaml
deleted file mode 100644
index 7cf856d..0000000
--- a/infra/apps/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
-patches:
- - target:
- kind: Deployment
- name: bff
- patch: |-
- - op: replace
- path: /spec/replicas
- value: 2
- - op: replace
- path: /spec/template/spec/containers/0/envFrom/0/configMapRef/name
- value: stage-bff-config
- - target:
- kind: ConfigMap
- name: bff-config
- patch: |-
- - op: replace
- path: /data/NODE_ENV
- value: "staging"
diff --git a/infra/docker/prometheus/prometheus.yml b/infra/docker/prometheus/prometheus.yml
deleted file mode 100644
index e7cdf31..0000000
--- a/infra/docker/prometheus/prometheus.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-global:
- scrape_interval: 15s
- evaluation_interval: 15s
-
-scrape_configs:
- - job_name: "prometheus"
- static_configs:
- - targets: ["localhost:9090"]
-
- - job_name: "redpanda"
- static_configs:
- - targets: ["redpanda:9644"]
-
- #! В будущем дописать, далеком будущем !
- # - job_name: 'cpp-microservices'
- # static_configs:
- # - targets: ['ingest:8081', 'transcoder:8081', 'chat:8081']
diff --git a/infra/environments/dev/kustomization.yaml b/infra/environments/dev/kustomization.yaml
deleted file mode 100644
index fec4103..0000000
--- a/infra/environments/dev/kustomization.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
- - ../../namespaces/overlays/dev
- - ../../postgres/overlays/dev
- - ../../redis/overlays/dev
- - ../../kafka/overlays/dev
- - ../../s3/overlays/dev
- - ../../monitoring/overlays/dev
- - ../../apps/overlays/dev
diff --git a/infra/environments/prod/kustomization.yaml b/infra/environments/prod/kustomization.yaml
deleted file mode 100644
index ccc081b..0000000
--- a/infra/environments/prod/kustomization.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
- - ../../namespaces/overlays/prod
- - ../../postgres/overlays/prod
- - ../../redis/overlays/prod
- - ../../kafka/overlays/prod
- - ../../s3/overlays/prod
- - ../../monitoring/overlays/prod
- - ../../apps/overlays/prod
diff --git a/infra/environments/stage/kustomization.yaml b/infra/environments/stage/kustomization.yaml
deleted file mode 100644
index bac3682..0000000
--- a/infra/environments/stage/kustomization.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-resources:
- - ../../namespaces/overlays/stage
- - ../../postgres/overlays/stage
- - ../../redis/overlays/stage
- - ../../kafka/overlays/stage
- - ../../s3/overlays/stage
- - ../../monitoring/overlays/stage
- - ../../apps/overlays/stage
diff --git a/infra/kafka/base/kustomization.yaml b/infra/kafka/base/kustomization.yaml
deleted file mode 100644
index 1d5091b..0000000
--- a/infra/kafka/base/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - redpanda.yaml
diff --git a/infra/kafka/base/redpanda.yaml b/infra/kafka/base/redpanda.yaml
deleted file mode 100644
index 7c6296f..0000000
--- a/infra/kafka/base/redpanda.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: apps/v1
-kind: StatefulSet
-metadata:
- name: redpanda
- namespace: data-layer
-spec:
- serviceName: redpanda
- replicas: 1
- selector:
- matchLabels:
- app: redpanda
- template:
- metadata:
- labels:
- app: redpanda
- spec:
- containers:
- - name: redpanda
- image: docker.redpanda.com/redpandadata/redpanda:v23.2.1
- command:
- - /bin/sh
- - -c
- - |
- /usr/bin/rpk redpanda start \
- --mode dev-container \
- --smp 1 \
- --memory 512M \
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092 \
- --advertise-kafka-addr internal://redpanda.data-layer.svc.cluster.local:9092,external://localhost:19092
- ports:
- - containerPort: 9092
- - containerPort: 19092
- resources:
- requests:
- memory: "256Mi"
- cpu: "200m"
- limits:
- memory: "768Mi"
- cpu: "1000m"
----
-apiVersion: v1
-kind: Service
-metadata:
- name: redpanda
- namespace: data-layer
-spec:
- ports:
- - name: kafka-internal
- port: 9092
- - name: kafka-external
- port: 19092
- selector:
- app: redpanda
diff --git a/infra/kafka/overlays/dev/kustomization.yaml b/infra/kafka/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/kafka/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/kafka/overlays/prod/kustomization.yaml b/infra/kafka/overlays/prod/kustomization.yaml
deleted file mode 100644
index eb9d39f..0000000
--- a/infra/kafka/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-patches:
- - target:
- kind: StatefulSet
- name: redpanda
- patch: |-
- - op: replace
- path: /spec/replicas
- value: 3
- - op: replace
- path: /spec/template/spec/containers/0/resources/limits/memory
- value: "4Gi"
diff --git a/infra/kafka/overlays/stage/kustomization.yaml b/infra/kafka/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/kafka/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
diff --git a/infra/monitoring/base/grafana/deployment.yaml b/infra/monitoring/base/grafana/deployment.yaml
deleted file mode 100644
index fa5dfd6..0000000
--- a/infra/monitoring/base/grafana/deployment.yaml
+++ /dev/null
@@ -1,89 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: grafana-datasources
- namespace: monitoring
-data:
- prometheus.yaml: |-
- apiVersion: 1
- datasources:
- - name: Prometheus
- type: prometheus
- url: http://prometheus.monitoring.svc.cluster.local:9090
- access: proxy
- isDefault: true
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: grafana-dashboards-provider
- namespace: monitoring
-data:
- dashboards.yaml: |-
- apiVersion: 1
- providers:
- - name: 'default'
- orgId: 1
- folder: ''
- type: file
- disableDeletion: false
- editable: true
- options:
- path: /var/lib/grafana/dashboards
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: grafana
- namespace: monitoring
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: grafana
- template:
- metadata:
- labels:
- app: grafana
- spec:
- containers:
- - name: grafana
- image: grafana/grafana-oss:10.0.3
- ports:
- - containerPort: 3000
- env:
- - name: GF_SECURITY_ADMIN_PASSWORD
- value: "admin" # в секретик потом!
- - name: GF_USERS_ALLOW_SIGN_UP
- value: "false"
- resources:
- requests:
- memory: "128Mi"
- cpu: "100m"
- limits:
- memory: "256Mi"
- cpu: "300m"
- volumeMounts:
- - name: grafana-datasources
- mountPath: /etc/grafana/provisioning/datasources
- - name: grafana-dashboards-provider
- mountPath: /etc/grafana/provisioning/dashboards
- volumes:
- - name: grafana-datasources
- configMap:
- name: grafana-datasources
- - name: grafana-dashboards-provider
- configMap:
- name: grafana-dashboards-provider
----
-apiVersion: v1
-kind: Service
-metadata:
- name: grafana
- namespace: monitoring
-spec:
- ports:
- - port: 80
- targetPort: 3000
- selector:
- app: grafana
diff --git a/infra/monitoring/base/kustomization.yaml b/infra/monitoring/base/kustomization.yaml
deleted file mode 100644
index 466fee7..0000000
--- a/infra/monitoring/base/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - prometheus/deployment.yaml
- - grafana/deployment.yaml
diff --git a/infra/monitoring/base/prometheus/deployment.yaml b/infra/monitoring/base/prometheus/deployment.yaml
deleted file mode 100644
index 2162fc2..0000000
--- a/infra/monitoring/base/prometheus/deployment.yaml
+++ /dev/null
@@ -1,118 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: prometheus-config
- namespace: monitoring
-data:
- prometheus.yml: |-
- global:
- scrape_interval: 15s # Чаще для live-платформы, но не 1s чтобы не убить RAM
- evaluation_interval: 15s
- scrape_configs:
- - job_name: 'prometheus'
- static_configs:
- - targets: ['localhost:9090']
- # Scrape C++ Microservices
- - job_name: 'cpp-microservices'
- kubernetes_sd_configs:
- - role: pod
- relabel_configs:
- - source_labels: [__meta_kubernetes_pod_label_app]
- regex: 'ingest|transcoder|chat|bff|auth'
- action: keep
- - source_labels: [__meta_kubernetes_pod_container_port_name]
- regex: 'metrics'
- action: keep
- # Scrape Kafka (Redpanda)
- - job_name: 'redpanda'
- static_configs:
- - targets: ['redpanda.data-layer.svc.cluster.local:9644']
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: prometheus
- namespace: monitoring
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: prometheus
- template:
- metadata:
- labels:
- app: prometheus
- spec:
- containers:
- - name: prometheus
- image: prom/prometheus:v2.45.0
- args:
- - "--config.file=/etc/prometheus/prometheus.yml"
- - "--storage.tsdb.path=/prometheus"
- - "--storage.tsdb.retention.time=1d"
- - "--web.enable-lifecycle"
- ports:
- - containerPort: 9090
- resources:
- requests:
- memory: "128Mi"
- cpu: "100m"
- limits:
- memory: "256Mi"
- cpu: "500m"
- volumeMounts:
- - name: prometheus-config
- mountPath: /etc/prometheus/
- - name: prometheus-data
- mountPath: /prometheus
- volumes:
- - name: prometheus-config
- configMap:
- name: prometheus-config
- - name: prometheus-data
- emptyDir: {}
----
-apiVersion: v1
-kind: Service
-metadata:
- name: prometheus
- namespace: monitoring
-spec:
- ports:
- - port: 9090
- targetPort: 9090
- selector:
- app: prometheus
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: prometheus
-rules:
- - apiGroups: [""]
- resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
- verbs: ["get", "list", "watch"]
- - apiGroups: ["extensions", "networking.k8s.io"]
- resources: ["ingresses"]
- verbs: ["get", "list", "watch"]
- - nonResourceURLs: ["/metrics"]
- verbs: ["get"]
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: prometheus
- namespace: monitoring
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
- name: prometheus
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: prometheus
-subjects:
- - kind: ServiceAccount
- name: prometheus
- namespace: monitoring
diff --git a/infra/monitoring/overlays/dev/kustomization.yaml b/infra/monitoring/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/monitoring/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/monitoring/overlays/prod/kustomization.yaml b/infra/monitoring/overlays/prod/kustomization.yaml
deleted file mode 100644
index 2333422..0000000
--- a/infra/monitoring/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
diff --git a/infra/monitoring/overlays/stage/kustomization.yaml b/infra/monitoring/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/monitoring/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
diff --git a/infra/namespaces/base/kustomization.yaml b/infra/namespaces/base/kustomization.yaml
deleted file mode 100644
index a1eee58..0000000
--- a/infra/namespaces/base/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - namespaces.yaml
diff --git a/infra/namespaces/base/namespaces.yaml b/infra/namespaces/base/namespaces.yaml
deleted file mode 100644
index 00a3e8b..0000000
--- a/infra/namespaces/base/namespaces.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: v1
-kind: Namespace
-metadata:
- name: data-layer
- labels:
- name: data-layer
----
-apiVersion: v1
-kind: Namespace
-metadata:
- name: apps
- labels:
- name: apps
----
-apiVersion: v1
-kind: Namespace
-metadata:
- name: monitoring
- labels:
- name: monitoring
diff --git a/infra/namespaces/overlays/dev/kustomization.yaml b/infra/namespaces/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/namespaces/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/namespaces/overlays/prod/kustomization.yaml b/infra/namespaces/overlays/prod/kustomization.yaml
deleted file mode 100644
index 2333422..0000000
--- a/infra/namespaces/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
diff --git a/infra/namespaces/overlays/stage/kustomization.yaml b/infra/namespaces/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/namespaces/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
diff --git a/infra/postgres/base/kustomization.yaml b/infra/postgres/base/kustomization.yaml
deleted file mode 100644
index b9031ef..0000000
--- a/infra/postgres/base/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - statefulset.yaml
diff --git a/infra/postgres/base/statefulset.yaml b/infra/postgres/base/statefulset.yaml
deleted file mode 100644
index 3cb95e1..0000000
--- a/infra/postgres/base/statefulset.yaml
+++ /dev/null
@@ -1,57 +0,0 @@
-apiVersion: apps/v1
-kind: StatefulSet
-metadata:
- name: postgres
- namespace: data-layer
-spec:
- serviceName: postgres
- replicas: 1
- selector:
- matchLabels:
- app: postgres
- template:
- metadata:
- labels:
- app: postgres
- spec:
- containers:
- - name: postgres
- image: postgres:16-alpine
- env:
- - name: POSTGRES_USER
- value: "itter_admin"
- - name: POSTGRES_PASSWORD
- value: "secret_password"
- - name: POSTGRES_DB
- value: "itterstream"
- ports:
- - containerPort: 5432
- resources:
- requests:
- memory: "128Mi"
- cpu: "100m"
- limits:
- memory: "256Mi"
- cpu: "500m"
- volumeMounts:
- - name: pgdata
- mountPath: /var/lib/postgresql/data
- volumeClaimTemplates:
- - metadata:
- name: pgdata
- spec:
- accessModes: ["ReadWriteOnce"]
- resources:
- requests:
- storage: 2Gi
----
-apiVersion: v1
-kind: Service
-metadata:
- name: postgres
- namespace: data-layer
-spec:
- ports:
- - port: 5432
- selector:
- app: postgres
diff --git a/infra/postgres/overlays/dev/kustomization.yaml b/infra/postgres/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/postgres/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/postgres/overlays/prod/kustomization.yaml b/infra/postgres/overlays/prod/kustomization.yaml
deleted file mode 100644
index f0d24e4..0000000
--- a/infra/postgres/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-patches:
- - target:
- kind: StatefulSet
- name: postgres
- patch: |-
- - op: replace
- path: /spec/replicas
- value: 3
- - op: replace
- path: /spec/template/spec/containers/0/resources/limits/memory
- value: "4Gi"
diff --git a/infra/postgres/overlays/stage/kustomization.yaml b/infra/postgres/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/postgres/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
diff --git a/infra/redis/base/deployment.yaml b/infra/redis/base/deployment.yaml
deleted file mode 100644
index 3407799..0000000
--- a/infra/redis/base/deployment.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: redis
- namespace: data-layer
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: redis
- template:
- metadata:
- labels:
- app: redis
- spec:
- containers:
- - name: redis
- image: redis:7-alpine
- command: ["redis-server", "--appendonly", "yes"]
- ports:
- - containerPort: 6379
- resources:
- requests:
- memory: "64Mi"
- cpu: "50m"
- limits:
- memory: "128Mi"
- cpu: "200m"
- volumeMounts:
- - name: redis-data
- mountPath: /data
- volumes:
- - name: redis-data
- emptyDir: {} # доделать, я спать
----
-apiVersion: v1
-kind: Service
-metadata:
- name: redis
- namespace: data-layer
-spec:
- ports:
- - port: 6379
- selector:
- app: redis
diff --git a/infra/redis/base/kustomization.yaml b/infra/redis/base/kustomization.yaml
deleted file mode 100644
index 9c2d28b..0000000
--- a/infra/redis/base/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - deployment.yaml
diff --git a/infra/redis/overlays/dev/kustomization.yaml b/infra/redis/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/redis/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/redis/overlays/prod/kustomization.yaml b/infra/redis/overlays/prod/kustomization.yaml
deleted file mode 100644
index f928b81..0000000
--- a/infra/redis/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-patches:
- - target:
- kind: Deployment
- name: redis
- patch: |-
- - op: replace
- path: /spec/replicas
- value: 3
- - op: replace
- path: /spec/template/spec/containers/0/resources/limits/memory
- value: "2Gi"
diff --git a/infra/redis/overlays/stage/kustomization.yaml b/infra/redis/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/redis/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-
diff --git a/infra/s3/base/deployment.yaml b/infra/s3/base/deployment.yaml
deleted file mode 100644
index bf9c896..0000000
--- a/infra/s3/base/deployment.yaml
+++ /dev/null
@@ -1,60 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: minio
- namespace: data-layer
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: minio
- template:
- metadata:
- labels:
- app: minio
- spec:
- containers:
- - name: minio
- image: minio/minio:RELEASE.2023-08-09T23-30-22Z
- args:
- - server
- - /data
- - --console-address
- - ":9001"
- env:
- - name: MINIO_ROOT_USER
- value: "minioadmin"
- - name: MINIO_ROOT_PASSWORD
- value: "minioadmin"
- ports:
- - containerPort: 9000
- - containerPort: 9001
- resources:
- requests:
- memory: "128Mi"
- cpu: "100m"
- limits:
- memory: "256Mi"
- cpu: "500m"
- volumeMounts:
- - name: minio-data
- mountPath: /data
- volumes:
- - name: minio-data
- emptyDir: {}
----
-apiVersion: v1
-kind: Service
-metadata:
- name: minio
- namespace: data-layer
-spec:
- ports:
- - name: api
- port: 9000
- targetPort: 9000
- - name: console
- port: 9001
- targetPort: 9001
- selector:
- app: minio
diff --git a/infra/s3/base/kustomization.yaml b/infra/s3/base/kustomization.yaml
deleted file mode 100644
index 9c2d28b..0000000
--- a/infra/s3/base/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - deployment.yaml
diff --git a/infra/s3/overlays/dev/kustomization.yaml b/infra/s3/overlays/dev/kustomization.yaml
deleted file mode 100644
index 88dffbe..0000000
--- a/infra/s3/overlays/dev/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: dev-
diff --git a/infra/s3/overlays/prod/kustomization.yaml b/infra/s3/overlays/prod/kustomization.yaml
deleted file mode 100644
index 2333422..0000000
--- a/infra/s3/overlays/prod/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
diff --git a/infra/s3/overlays/stage/kustomization.yaml b/infra/s3/overlays/stage/kustomization.yaml
deleted file mode 100644
index 0ba21da..0000000
--- a/infra/s3/overlays/stage/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
- - ../../base
-namePrefix: stage-