From 4b7415e1c3daeaacf092fffaa564cf254b637493 Mon Sep 17 00:00:00 2001 From: mohamad Date: Sun, 30 Mar 2025 16:02:49 +0200 Subject: [PATCH] =?UTF-8?q?weeee=F0=9F=92=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/.dockerignore | 30 + be/.gitignore | 141 + be/Dockerfile | 35 + be/alembic.ini | 119 + be/alembic/README | 1 + be/alembic/env.py | 96 + be/alembic/script.py.mako | 28 + .../643956b3f4de_initial_database_setup.py | 32 + ...075e73a_add_user_group_usergroup_models.py | 72 + .../c6cbef99588b_initial_database_setup.py | 32 + be/app/__init__.py | 0 be/app/api/__init__.py | 0 be/app/api/api_router.py | 12 + be/app/api/v1/__init__.py | 0 be/app/api/v1/api.py | 12 + be/app/api/v1/endpoints/__init__.py | 0 be/app/api/v1/endpoints/health.py | 45 + be/app/config.py | 24 + be/app/database.py | 47 + be/app/main.py | 90 + be/app/models.py | 108 + be/app/schemas/__init__.py | 0 be/app/schemas/health.py | 9 + be/requirements.txt | 8 + docker-compose.yml | 65 + fe/.gitignore | 23 + fe/.npmrc | 1 + fe/.prettierignore | 6 + fe/.prettierrc | 15 + fe/README.md | 38 + fe/package-lock.json | 2401 +++++++++++++++++ fe/package.json | 31 + fe/src/app.css | 2 + fe/src/app.d.ts | 13 + fe/src/app.html | 17 + fe/src/lib/apiClient.ts | 163 ++ fe/src/lib/index.ts | 1 + fe/src/lib/schemas/health.ts | 4 + fe/src/routes/+layout.svelte | 41 + fe/src/routes/+page.svelte | 0 fe/src/service-worker.ts | 64 + fe/static/favicon.png | Bin 0 -> 1571 bytes fe/static/icon-144x144.png | Bin 0 -> 7314 bytes fe/static/icon-192x192.png | Bin 0 -> 7314 bytes fe/static/icon-512x512.png | Bin 0 -> 19015 bytes fe/static/manifest.json | 29 + fe/svelte.config.js | 9 + fe/tsconfig.json | 19 + fe/vite.config.ts | 7 + 49 files changed, 3890 insertions(+) create mode 100644 be/.dockerignore create mode 100644 be/.gitignore create mode 100644 be/Dockerfile create mode 100644 be/alembic.ini create mode 100644 be/alembic/README create mode 100644 be/alembic/env.py create mode 100644 be/alembic/script.py.mako create mode 100644 be/alembic/versions/643956b3f4de_initial_database_setup.py create mode 100644 be/alembic/versions/85a3c075e73a_add_user_group_usergroup_models.py create mode 100644 be/alembic/versions/c6cbef99588b_initial_database_setup.py create mode 100644 be/app/__init__.py create mode 100644 be/app/api/__init__.py create mode 100644 be/app/api/api_router.py create mode 100644 be/app/api/v1/__init__.py create mode 100644 be/app/api/v1/api.py create mode 100644 be/app/api/v1/endpoints/__init__.py create mode 100644 be/app/api/v1/endpoints/health.py create mode 100644 be/app/config.py create mode 100644 be/app/database.py create mode 100644 be/app/main.py create mode 100644 be/app/models.py create mode 100644 be/app/schemas/__init__.py create mode 100644 be/app/schemas/health.py create mode 100644 be/requirements.txt create mode 100644 docker-compose.yml create mode 100644 fe/.gitignore create mode 100644 fe/.npmrc create mode 100644 fe/.prettierignore create mode 100644 fe/.prettierrc create mode 100644 fe/README.md create mode 100644 fe/package-lock.json create mode 100644 fe/package.json create mode 100644 fe/src/app.css create mode 100644 fe/src/app.d.ts create mode 100644 fe/src/app.html create mode 100644 fe/src/lib/apiClient.ts create mode 100644 fe/src/lib/index.ts create mode 100644 fe/src/lib/schemas/health.ts create mode 100644 fe/src/routes/+layout.svelte create mode 100644 fe/src/routes/+page.svelte create mode 100644 fe/src/service-worker.ts create mode 100644 fe/static/favicon.png create mode 100644 fe/static/icon-144x144.png create mode 100644 fe/static/icon-192x192.png create mode 100644 fe/static/icon-512x512.png create mode 100644 fe/static/manifest.json create mode 100644 fe/svelte.config.js create mode 100644 fe/tsconfig.json create mode 100644 fe/vite.config.ts diff --git a/be/.dockerignore b/be/.dockerignore new file mode 100644 index 0000000..405b1e0 --- /dev/null +++ b/be/.dockerignore @@ -0,0 +1,30 @@ +# Git files +.git +.gitignore + +# Virtual environment +.venv +venv/ +env/ +ENV/ +*.env # Ignore local .env files within the backend directory if any + +# Python cache +__pycache__/ +*.py[cod] +*$py.class + +# IDE files +.idea/ +.vscode/ + +# Test artifacts +.pytest_cache/ +htmlcov/ +.coverage* + +# Other build/temp files +*.egg-info/ +dist/ +build/ +*.db # e.g., sqlite temp dbs \ No newline at end of file diff --git a/be/.gitignore b/be/.gitignore new file mode 100644 index 0000000..612cdbb --- /dev/null +++ b/be/.gitignore @@ -0,0 +1,141 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# PEP 582; used by PDM, Flit and potentially other tools. +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static analysis results +.pytype/ + +# alembic default temp file +*.db # If using sqlite for alembic versions locally for instance + +# If you use alembic autogenerate, it might create temporary files +# Depending on your DB, adjust if necessary +# *.sql.tmp + +# IDE files +.idea/ +.vscode/ + +# OS generated files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/be/Dockerfile b/be/Dockerfile new file mode 100644 index 0000000..a2f5925 --- /dev/null +++ b/be/Dockerfile @@ -0,0 +1,35 @@ +# be/Dockerfile + +# Choose a suitable Python base image +FROM python:3.11-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 # Prevent python from writing pyc files +ENV PYTHONUNBUFFERED 1 # Keep stdout/stderr unbuffered + +# Set the working directory in the container +WORKDIR /app + +# Install system dependencies if needed (e.g., for psycopg2 build) +# RUN apt-get update && apt-get install -y --no-install-recommends gcc build-essential libpq-dev && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +# Upgrade pip first +RUN pip install --no-cache-dir --upgrade pip +# Copy only requirements first to leverage Docker cache +COPY requirements.txt requirements.txt +# Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application code into the working directory +COPY . . +# This includes your 'app/' directory, alembic.ini, etc. + +# Expose the port the app runs on +EXPOSE 8000 + +# Command to run the application using uvicorn +# The default command for production (can be overridden in docker-compose for development) +# Note: Make sure 'app.main:app' correctly points to your FastAPI app instance +# relative to the WORKDIR (/app). If your main.py is directly in /app, this is correct. +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/be/alembic.ini b/be/alembic.ini new file mode 100644 index 0000000..69a212a --- /dev/null +++ b/be/alembic.ini @@ -0,0 +1,119 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +# version_path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +; sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/be/alembic/README b/be/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/be/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/be/alembic/env.py b/be/alembic/env.py new file mode 100644 index 0000000..7dc2d38 --- /dev/null +++ b/be/alembic/env.py @@ -0,0 +1,96 @@ +from logging.config import fileConfig +import os +import sys + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + + +# Ensure the 'app' directory is in the Python path +# Adjust the path if your project structure is different +sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))) + +# Import your app's Base and settings +from app.models import Base # Import Base from your models module +from app.config import settings # Import settings to get DATABASE_URL + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Set the sqlalchemy.url from your application settings +# Use a synchronous version of the URL for Alembic's operations +sync_db_url = settings.DATABASE_URL.replace("+asyncpg", "") if settings.DATABASE_URL else None +if not sync_db_url: + raise ValueError("DATABASE_URL not found in settings for Alembic.") +config.set_main_option('sqlalchemy.url', sync_db_url) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/be/alembic/script.py.mako b/be/alembic/script.py.mako new file mode 100644 index 0000000..480b130 --- /dev/null +++ b/be/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/be/alembic/versions/643956b3f4de_initial_database_setup.py b/be/alembic/versions/643956b3f4de_initial_database_setup.py new file mode 100644 index 0000000..27d7c44 --- /dev/null +++ b/be/alembic/versions/643956b3f4de_initial_database_setup.py @@ -0,0 +1,32 @@ +"""Initial database setup + +Revision ID: 643956b3f4de +Revises: +Create Date: 2025-03-29 20:49:01.018626 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '643956b3f4de' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/be/alembic/versions/85a3c075e73a_add_user_group_usergroup_models.py b/be/alembic/versions/85a3c075e73a_add_user_group_usergroup_models.py new file mode 100644 index 0000000..d389bcb --- /dev/null +++ b/be/alembic/versions/85a3c075e73a_add_user_group_usergroup_models.py @@ -0,0 +1,72 @@ +"""Add User, Group, UserGroup models + +Revision ID: 85a3c075e73a +Revises: c6cbef99588b +Create Date: 2025-03-30 12:46:07.322285 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '85a3c075e73a' +down_revision: Union[str, None] = 'c6cbef99588b' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('password_hash', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) + op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False) + op.create_index(op.f('ix_users_name'), 'users', ['name'], unique=False) + op.create_table('groups', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('created_by_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['created_by_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_groups_id'), 'groups', ['id'], unique=False) + op.create_index(op.f('ix_groups_name'), 'groups', ['name'], unique=False) + op.create_table('user_groups', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.Column('role', sa.Enum('owner', 'member', name='userroleenum'), nullable=False), + sa.Column('joined_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id', 'group_id', name='uq_user_group') + ) + op.create_index(op.f('ix_user_groups_id'), 'user_groups', ['id'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_user_groups_id'), table_name='user_groups') + op.drop_table('user_groups') + op.drop_index(op.f('ix_groups_name'), table_name='groups') + op.drop_index(op.f('ix_groups_id'), table_name='groups') + op.drop_table('groups') + op.drop_index(op.f('ix_users_name'), table_name='users') + op.drop_index(op.f('ix_users_id'), table_name='users') + op.drop_index(op.f('ix_users_email'), table_name='users') + op.drop_table('users') + # ### end Alembic commands ### diff --git a/be/alembic/versions/c6cbef99588b_initial_database_setup.py b/be/alembic/versions/c6cbef99588b_initial_database_setup.py new file mode 100644 index 0000000..3b37977 --- /dev/null +++ b/be/alembic/versions/c6cbef99588b_initial_database_setup.py @@ -0,0 +1,32 @@ +"""Initial database setup + +Revision ID: c6cbef99588b +Revises: 643956b3f4de +Create Date: 2025-03-30 12:18:51.207858 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'c6cbef99588b' +down_revision: Union[str, None] = '643956b3f4de' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/be/app/__init__.py b/be/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/be/app/api/__init__.py b/be/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/be/app/api/api_router.py b/be/app/api/api_router.py new file mode 100644 index 0000000..23b7759 --- /dev/null +++ b/be/app/api/api_router.py @@ -0,0 +1,12 @@ +# app/api/api_router.py +from fastapi import APIRouter + +from app.api.v1.api import api_router_v1 # Import the v1 router + +api_router = APIRouter() + +# Include versioned routers here, adding the /api prefix +api_router.include_router(api_router_v1, prefix="/v1") # Mounts v1 endpoints under /api/v1/... + +# Add other API versions later +# e.g., api_router.include_router(api_router_v2, prefix="/v2") \ No newline at end of file diff --git a/be/app/api/v1/__init__.py b/be/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/be/app/api/v1/api.py b/be/app/api/v1/api.py new file mode 100644 index 0000000..c8b9f7c --- /dev/null +++ b/be/app/api/v1/api.py @@ -0,0 +1,12 @@ +# app/api/v1/api.py +from fastapi import APIRouter + +from app.api.v1.endpoints import health # Import the health endpoint router + +api_router_v1 = APIRouter() + +# Include endpoint routers here, adding the desired prefix for v1 +api_router_v1.include_router(health.router) # The path "/health" is defined inside health.router + +# Add other v1 endpoint routers here later +# e.g., api_router_v1.include_router(users.router, prefix="/users", tags=["Users"]) \ No newline at end of file diff --git a/be/app/api/v1/endpoints/__init__.py b/be/app/api/v1/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/be/app/api/v1/endpoints/health.py b/be/app/api/v1/endpoints/health.py new file mode 100644 index 0000000..11de368 --- /dev/null +++ b/be/app/api/v1/endpoints/health.py @@ -0,0 +1,45 @@ +# app/api/v1/endpoints/health.py +import logging +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.sql import text + +from app.database import get_db # Import the dependency function +from app.schemas.health import HealthStatus # Import the response schema + +logger = logging.getLogger(__name__) +router = APIRouter() + +@router.get( + "/health", + response_model=HealthStatus, + summary="Perform a Health Check", + description="Checks the operational status of the API and its connection to the database.", + tags=["Health"] # Group this endpoint in Swagger UI +) +async def check_health(db: AsyncSession = Depends(get_db)): + """ + Health check endpoint. Verifies API reachability and database connection. + """ + try: + # Try executing a simple query to check DB connection + result = await db.execute(text("SELECT 1")) + if result.scalar_one() == 1: + logger.info("Health check successful: Database connection verified.") + return HealthStatus(status="ok", database="connected") + else: + # This case should ideally not happen with 'SELECT 1' + logger.error("Health check failed: Database connection check returned unexpected result.") + # Raise 503 Service Unavailable + raise HTTPException( + status_code=503, + detail="Database connection error: Unexpected result" + ) + + except Exception as e: + logger.error(f"Health check failed: Database connection error - {e}", exc_info=True) # Log stack trace + # Raise 503 Service Unavailable + raise HTTPException( + status_code=503, + detail=f"Database connection error: {e}" + ) \ No newline at end of file diff --git a/be/app/config.py b/be/app/config.py new file mode 100644 index 0000000..1b9f8a4 --- /dev/null +++ b/be/app/config.py @@ -0,0 +1,24 @@ +# app/config.py +import os +from pydantic_settings import BaseSettings +from dotenv import load_dotenv + +load_dotenv() + +class Settings(BaseSettings): + DATABASE_URL: str | None = None + + class Config: + env_file = ".env" + env_file_encoding = 'utf-8' + extra = "ignore" + +settings = Settings() + +# Basic validation to ensure DATABASE_URL is set +if settings.DATABASE_URL is None: + print("Error: DATABASE_URL environment variable not set.") + # Consider raising an exception for clearer failure + # raise ValueError("DATABASE_URL environment variable not set.") +# else: # Optional: Log the URL being used (without credentials ideally) for debugging + # print(f"DATABASE_URL loaded: {settings.DATABASE_URL[:settings.DATABASE_URL.find('@')] if '@' in settings.DATABASE_URL else 'URL structure unexpected'}") diff --git a/be/app/database.py b/be/app/database.py new file mode 100644 index 0000000..ca3c74d --- /dev/null +++ b/be/app/database.py @@ -0,0 +1,47 @@ +# app/database.py +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, declarative_base +from app.config import settings + +# Ensure DATABASE_URL is set before proceeding +if not settings.DATABASE_URL: + raise ValueError("DATABASE_URL is not configured in settings.") + +# Create the SQLAlchemy async engine +# pool_recycle=3600 helps prevent stale connections on some DBs +engine = create_async_engine( + settings.DATABASE_URL, + echo=True, # Log SQL queries (useful for debugging) + future=True, # Use SQLAlchemy 2.0 style features + pool_recycle=3600 # Optional: recycle connections after 1 hour +) + +# Create a configured "Session" class +# expire_on_commit=False prevents attributes from expiring after commit +AsyncSessionLocal = sessionmaker( + bind=engine, + class_=AsyncSession, + expire_on_commit=False, + autoflush=False, + autocommit=False, +) + +# Base class for our ORM models +Base = declarative_base() + +# Dependency to get DB session in path operations +async def get_db() -> AsyncSession: # type: ignore + """ + Dependency function that yields an AsyncSession. + Ensures the session is closed after the request. + """ + async with AsyncSessionLocal() as session: + try: + yield session + # Optionally commit if your endpoints modify data directly + # await session.commit() # Usually commit happens within endpoint logic + except Exception: + await session.rollback() + raise + finally: + await session.close() # Not strictly necessary with async context manager, but explicit \ No newline at end of file diff --git a/be/app/main.py b/be/app/main.py new file mode 100644 index 0000000..fd27fcd --- /dev/null +++ b/be/app/main.py @@ -0,0 +1,90 @@ +# app/main.py +import logging +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api.api_router import api_router # Import the main combined router +# Import database and models if needed for startup/shutdown events later +# from . import database, models + +# --- Logging Setup --- +# Configure logging (can be more sophisticated later, e.g., using logging.yaml) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# --- FastAPI App Instance --- +app = FastAPI( + title="Shared Lists API", + description="API for managing shared shopping lists, OCR, and cost splitting.", + version="0.1.0", + openapi_url="/api/openapi.json", # Place OpenAPI spec under /api + docs_url="/api/docs", # Place Swagger UI under /api + redoc_url="/api/redoc" # Place ReDoc under /api +) + +# --- CORS Middleware --- +# Define allowed origins. Be specific in production! +# Use ["*"] for wide open access during early development if needed, +# but restrict it as soon as possible. +# SvelteKit default dev port is 5173 +origins = [ + "http://localhost:5173", + "http://localhost:8000", # Allow requests from the API itself (e.g., Swagger UI) + # Add your deployed frontend URL here later + # "https://your-frontend-domain.com", +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, # List of origins that are allowed to make requests + allow_credentials=True, # Allow cookies to be included in requests + allow_methods=["*"], # Allow all methods (GET, POST, PUT, DELETE, etc.) + allow_headers=["*"], # Allow all headers +) +# --- End CORS Middleware --- + + +# --- Include API Routers --- +# All API endpoints will be prefixed with /api +app.include_router(api_router, prefix="/api") +# --- End Include API Routers --- + + +# --- Root Endpoint (Optional - outside the main API structure) --- +@app.get("/", tags=["Root"]) +async def read_root(): + """ + Provides a simple welcome message at the root path. + Useful for basic reachability checks. + """ + logger.info("Root endpoint '/' accessed.") + # You could redirect to the docs or return a simple message + # from fastapi.responses import RedirectResponse + # return RedirectResponse(url="/api/docs") + return {"message": "Welcome to the Shared Lists API! Docs available at /api/docs"} +# --- End Root Endpoint --- + + +# --- Application Startup/Shutdown Events (Optional) --- +# @app.on_event("startup") +# async def startup_event(): +# logger.info("Application startup: Connecting to database...") +# # You might perform initial checks or warm-up here +# # await database.engine.connect() # Example check (get_db handles sessions per request) +# logger.info("Application startup complete.") + +# @app.on_event("shutdown") +# async def shutdown_event(): +# logger.info("Application shutdown: Disconnecting from database...") +# # await database.engine.dispose() # Close connection pool +# logger.info("Application shutdown complete.") +# --- End Events --- + + +# --- Direct Run (for simple local testing if needed) --- +# It's better to use `uvicorn app.main:app --reload` from the terminal +# if __name__ == "__main__": +# logger.info("Starting Uvicorn server directly from main.py") +# uvicorn.run(app, host="0.0.0.0", port=8000) +# ------------------------------------------------------ \ No newline at end of file diff --git a/be/app/models.py b/be/app/models.py new file mode 100644 index 0000000..72d93d4 --- /dev/null +++ b/be/app/models.py @@ -0,0 +1,108 @@ +# app/models.py +import enum +from datetime import datetime +from sqlalchemy import ( + Column, + Integer, + String, + DateTime, + ForeignKey, + Boolean, + Enum as SAEnum, # Renamed to avoid clash with Python's enum + UniqueConstraint, + event, + DDL +) +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func # For server_default=func.now() + +from app.database import Base # Import Base from database setup + +# Define Enum for User Roles in Groups +class UserRoleEnum(enum.Enum): + owner = "owner" + member = "member" + +# --- User Model --- +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True, nullable=False) + password_hash = Column(String, nullable=False) + name = Column(String, index=True, nullable=True) # Allow nullable name initially + created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) + + # Relationships + # Groups created by this user + created_groups = relationship("Group", back_populates="creator") + # Association object for group membership + group_associations = relationship("UserGroup", back_populates="user", cascade="all, delete-orphan") + + # Items added by this user (Add later when Item model is defined) + # added_items = relationship("Item", foreign_keys="[Item.added_by_id]", back_populates="added_by_user") + # Items completed by this user (Add later) + # completed_items = relationship("Item", foreign_keys="[Item.completed_by_id]", back_populates="completed_by_user") + # Expense shares for this user (Add later) + # expense_shares = relationship("ExpenseShare", back_populates="user") + # Lists created by this user (Add later) + # created_lists = relationship("List", foreign_keys="[List.created_by_id]", back_populates="creator") + + +# --- Group Model --- +class Group(Base): + __tablename__ = "groups" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, index=True, nullable=False) + created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False) + created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) + + # Relationships + # The user who created this group + creator = relationship("User", back_populates="created_groups") + # Association object for group membership + member_associations = relationship("UserGroup", back_populates="group", cascade="all, delete-orphan") + # Lists belonging to this group (Add later) + # lists = relationship("List", back_populates="group") + +# --- UserGroup Association Model --- +class UserGroup(Base): + __tablename__ = "user_groups" + __table_args__ = (UniqueConstraint('user_id', 'group_id', name='uq_user_group'),) # Ensure user cannot be in same group twice + + id = Column(Integer, primary_key=True, index=True) # Surrogate primary key + user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) + group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=False) + role = Column(SAEnum(UserRoleEnum), nullable=False, default=UserRoleEnum.member) + joined_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) + + # Relationships back to User and Group + user = relationship("User", back_populates="group_associations") + group = relationship("Group", back_populates="member_associations") + + +# --- Add other models below when needed --- +# class List(Base): ... +# class Item(Base): ... +# class Expense(Base): ... +# class ExpenseShare(Base): ... + +# Optional: Trigger for automatically creating an 'owner' UserGroup entry when a Group is created. +# This requires importing event and DDL. It's advanced and DB-specific, might be simpler to handle in application logic. +# Example for PostgreSQL (might need adjustment): +# group_owner_trigger = DDL(""" +# CREATE OR REPLACE FUNCTION add_group_owner() +# RETURNS TRIGGER AS $$ +# BEGIN +# INSERT INTO user_groups (user_id, group_id, role, joined_at) +# VALUES (NEW.created_by_id, NEW.id, 'owner', NOW()); +# RETURN NEW; +# END; +# $$ LANGUAGE plpgsql; +# +# CREATE TRIGGER trg_add_group_owner +# AFTER INSERT ON groups +# FOR EACH ROW EXECUTE FUNCTION add_group_owner(); +# """) +# event.listen(Group.__table__, 'after_create', group_owner_trigger) \ No newline at end of file diff --git a/be/app/schemas/__init__.py b/be/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/be/app/schemas/health.py b/be/app/schemas/health.py new file mode 100644 index 0000000..1d4f6e5 --- /dev/null +++ b/be/app/schemas/health.py @@ -0,0 +1,9 @@ +# app/schemas/health.py +from pydantic import BaseModel + +class HealthStatus(BaseModel): + """ + Response model for the health check endpoint. + """ + status: str = "ok" # Provide a default value + database: str \ No newline at end of file diff --git a/be/requirements.txt b/be/requirements.txt new file mode 100644 index 0000000..cc940dc --- /dev/null +++ b/be/requirements.txt @@ -0,0 +1,8 @@ +fastapi>=0.95.0 +uvicorn[standard]>=0.20.0 +sqlalchemy[asyncio]>=2.0.0 # Core ORM + Async support +asyncpg>=0.27.0 # Async PostgreSQL driver +psycopg2-binary>=2.9.0 # Often needed by Alembic even if app uses asyncpg +alembic>=1.9.0 # Database migrations +pydantic-settings>=2.0.0 # For loading settings from .env +python-dotenv>=1.0.0 # To load .env file for scripts/alembic \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c292335 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,65 @@ +# docker-compose.yml (in project root) +version: '3.8' + +services: + db: + image: postgres:15 # Use a specific PostgreSQL version + container_name: postgres_db + environment: + POSTGRES_USER: dev_user # Define DB user + POSTGRES_PASSWORD: dev_password # Define DB password + POSTGRES_DB: dev_db # Define Database name + volumes: + - postgres_data:/var/lib/postgresql/data # Persist data using a named volume + ports: + - "5432:5432" # Expose PostgreSQL port to host (optional, for direct access) + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + restart: unless-stopped + + backend: + container_name: fastapi_backend + build: + context: ./be # Path to the directory containing the Dockerfile + dockerfile: Dockerfile + volumes: + # Mount local code into the container for development hot-reloading + # The code inside the container at /app will mirror your local ./be directory + - ./be:/app + ports: + - "8000:8000" # Map container port 8000 to host port 8000 + environment: + # Pass the database URL to the backend container + # Uses the service name 'db' as the host, and credentials defined above + # IMPORTANT: Use the correct async driver prefix if your app needs it! + - DATABASE_URL=postgresql+asyncpg://dev_user:dev_password@db:5432/dev_db + # Add other environment variables needed by the backend here + # - SOME_OTHER_VAR=some_value + depends_on: + db: # Wait for the db service to be healthy before starting backend + condition: service_healthy + command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] # Override CMD for development reload + restart: unless-stopped + + pgadmin: # Optional service for database administration + image: dpage/pgadmin4:latest + container_name: pgadmin4_server + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com # Change as needed + PGADMIN_DEFAULT_PASSWORD: admin_password # Change to a secure password + PGADMIN_CONFIG_SERVER_MODE: 'False' # Run in Desktop mode for easier local dev server setup + volumes: + - pgadmin_data:/var/lib/pgadmin # Persist pgAdmin configuration + ports: + - "5050:80" # Map container port 80 to host port 5050 + depends_on: + - db # Depends on the database service + restart: unless-stopped + +volumes: # Define named volumes for data persistence + postgres_data: + pgadmin_data: \ No newline at end of file diff --git a/fe/.gitignore b/fe/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/fe/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/fe/.npmrc b/fe/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/fe/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/fe/.prettierignore b/fe/.prettierignore new file mode 100644 index 0000000..6562bcb --- /dev/null +++ b/fe/.prettierignore @@ -0,0 +1,6 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb diff --git a/fe/.prettierrc b/fe/.prettierrc new file mode 100644 index 0000000..7ebb855 --- /dev/null +++ b/fe/.prettierrc @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/fe/README.md b/fe/README.md new file mode 100644 index 0000000..b5b2950 --- /dev/null +++ b/fe/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/fe/package-lock.json b/fe/package-lock.json new file mode 100644 index 0000000..0fa91d6 --- /dev/null +++ b/fe/package-lock.json @@ -0,0 +1,2401 @@ +{ + "name": "fe", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fe", + "version": "0.0.1", + "devDependencies": { + "@sveltejs/adapter-node": "^5.2.11", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/vite": "^4.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", + "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", + "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", + "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", + "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", + "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", + "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", + "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", + "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", + "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", + "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", + "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", + "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", + "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", + "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", + "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", + "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", + "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", + "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", + "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", + "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", + "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", + "integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz", + "integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "esm-env": "^1.2.2", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.0", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.15", + "vitefu": "^1.0.4" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.17.tgz", + "integrity": "sha512-LIdNwcqyY7578VpofXyqjH6f+3fP4nrz7FBLki5HpzqjYfXdF2m/eW18ZfoKePtDGg90Bvvfpov9d2gy5XVCbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "tailwindcss": "4.0.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.17.tgz", + "integrity": "sha512-B4OaUIRD2uVrULpAD1Yksx2+wNarQr2rQh65nXqaqbLY1jCd8fO+3KLh/+TH4Hzh2NTHQvgxVbPdUDOtLk7vAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-x64": "4.0.17", + "@tailwindcss/oxide-freebsd-x64": "4.0.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-x64-musl": "4.0.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.17" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.17.tgz", + "integrity": "sha512-3RfO0ZK64WAhop+EbHeyxGThyDr/fYhxPzDbEQjD2+v7ZhKTb2svTWy+KK+J1PHATus2/CQGAGp7pHY/8M8ugg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.17.tgz", + "integrity": "sha512-e1uayxFQCCDuzTk9s8q7MC5jFN42IY7nzcr5n0Mw/AcUHwD6JaBkXnATkD924ZsHyPDvddnusIEvkgLd2CiREg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.17.tgz", + "integrity": "sha512-d6z7HSdOKfXQ0HPlVx1jduUf/YtBuCCtEDIEFeBCzgRRtDsUuRtofPqxIVaSCUTOk5+OfRLonje6n9dF6AH8wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.17.tgz", + "integrity": "sha512-EjrVa6lx3wzXz3l5MsdOGtYIsRjgs5Mru6lDv4RuiXpguWeOb3UzGJ7vw7PEzcFadKNvNslEQqoAABeMezprxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.17.tgz", + "integrity": "sha512-65zXfCOdi8wuaY0Ye6qMR5LAXokHYtrGvo9t/NmxvSZtCCitXV/gzJ/WP5ksXPhff1SV5rov0S+ZIZU+/4eyCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.17.tgz", + "integrity": "sha512-+aaq6hJ8ioTdbJV5IA1WjWgLmun4T7eYLTvJIToiXLHy5JzUERRbIZjAcjgK9qXMwnvuu7rqpxzej+hGoEcG5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.17.tgz", + "integrity": "sha512-/FhWgZCdUGAeYHYnZKekiOC0aXFiBIoNCA0bwzkICiMYS5Rtx2KxFfMUXQVnl4uZRblG5ypt5vpPhVaXgGk80w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.17.tgz", + "integrity": "sha512-gELJzOHK6GDoIpm/539Golvk+QWZjxQcbkKq9eB2kzNkOvrP0xc5UPgO9bIMNt1M48mO8ZeNenCMGt6tfkvVBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.17.tgz", + "integrity": "sha512-68NwxcJrZn94IOW4TysMIbYv5AlM6So1luTlbYUDIGnKma1yTFGBRNEJ+SacJ3PZE2rgcTBNRHX1TB4EQ/XEHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.17.tgz", + "integrity": "sha512-AkBO8efP2/7wkEXkNlXzRD4f/7WerqKHlc6PWb5v0jGbbm22DFBLbIM19IJQ3b+tNewQZa+WnPOaGm0SmwMNjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.17.tgz", + "integrity": "sha512-7/DTEvXcoWlqX0dAlcN0zlmcEu9xSermuo7VNGX9tJ3nYMdo735SHvbrHDln1+LYfF6NhJ3hjbpbjkMOAGmkDg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.17.tgz", + "integrity": "sha512-HJbBYDlDVg5cvYZzECb6xwc1IDCEM3uJi3hEZp3BjZGCNGJcTsnCpan+z+VMW0zo6gR0U6O6ElqU1OoZ74Dhww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.0.17", + "@tailwindcss/oxide": "4.0.17", + "lightningcss": "1.29.2", + "tailwindcss": "4.0.17" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.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/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/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.5.tgz", + "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "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-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "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-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "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-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "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-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "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-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "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-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "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/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "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.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", + "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.38.0", + "@rollup/rollup-android-arm64": "4.38.0", + "@rollup/rollup-darwin-arm64": "4.38.0", + "@rollup/rollup-darwin-x64": "4.38.0", + "@rollup/rollup-freebsd-arm64": "4.38.0", + "@rollup/rollup-freebsd-x64": "4.38.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", + "@rollup/rollup-linux-arm-musleabihf": "4.38.0", + "@rollup/rollup-linux-arm64-gnu": "4.38.0", + "@rollup/rollup-linux-arm64-musl": "4.38.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-musl": "4.38.0", + "@rollup/rollup-linux-s390x-gnu": "4.38.0", + "@rollup/rollup-linux-x64-gnu": "4.38.0", + "@rollup/rollup-linux-x64-musl": "4.38.0", + "@rollup/rollup-win32-arm64-msvc": "4.38.0", + "@rollup/rollup-win32-ia32-msvc": "4.38.0", + "@rollup/rollup-win32-x64-msvc": "4.38.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/svelte": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.3.tgz", + "integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.3", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.5.tgz", + "integrity": "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.17.tgz", + "integrity": "sha512-OErSiGzRa6rLiOvaipsDZvLMSpsBZ4ysB4f0VKGXUrjw2jfkJRd6kjRKV2+ZmTCNvwtvgdDam5D7w6WXsdLJZw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", + "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/fe/package.json b/fe/package.json new file mode 100644 index 0000000..735c0ce --- /dev/null +++ b/fe/package.json @@ -0,0 +1,31 @@ +{ + "name": "fe", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ." + }, + "devDependencies": { + "@sveltejs/adapter-node": "^5.2.11", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/vite": "^4.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.0.0" + } +} \ No newline at end of file diff --git a/fe/src/app.css b/fe/src/app.css new file mode 100644 index 0000000..ffb96a1 --- /dev/null +++ b/fe/src/app.css @@ -0,0 +1,2 @@ +@import 'tailwindcss'; +@plugin '@tailwindcss/forms'; diff --git a/fe/src/app.d.ts b/fe/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/fe/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/fe/src/app.html b/fe/src/app.html new file mode 100644 index 0000000..448824e --- /dev/null +++ b/fe/src/app.html @@ -0,0 +1,17 @@ + + + + + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + + \ No newline at end of file diff --git a/fe/src/lib/apiClient.ts b/fe/src/lib/apiClient.ts new file mode 100644 index 0000000..3c1bdef --- /dev/null +++ b/fe/src/lib/apiClient.ts @@ -0,0 +1,163 @@ +// src/lib/apiClient.ts +import { error } from '@sveltejs/kit'; // SvelteKit's error helper + +// --- Configuration --- +// Get the base URL from environment variables provided by Vite/SvelteKit +// Ensure VITE_API_BASE_URL is set in your .env file (e.g., VITE_API_BASE_URL=http://localhost:8000/api) +const BASE_URL = import.meta.env.VITE_API_BASE_URL; + +if (!BASE_URL) { + console.error('VITE_API_BASE_URL is not defined. Please set it in your .env file.'); + // In a real app, you might throw an error here or have a default, + // but logging is often sufficient during development. +} + +export class ApiClientError extends Error { + status: number; + errorData: unknown; + + constructor(message: string, status: number, errorData: unknown = null) { + super(message); + this.name = 'ApiClientError'; + this.status = status; + this.errorData = errorData; + + // --- Corrected Conditional Check --- + // Check if the static method exists on the Error constructor object + if (typeof (Error as any).captureStackTrace === 'function') { + // Call it if it exists, casting Error to 'any' to bypass static type check + (Error as any).captureStackTrace(this, ApiClientError); + } + // else { + // Optional: Fallback if captureStackTrace is not available + // You might assign the stack from a new error instance, + // though `super(message)` often handles basic stack creation. + // this.stack = new Error(message).stack; + // } + // --- End Corrected Check --- + } +} + +// --- Core Fetch Function --- +interface RequestOptions extends Omit { + // Can add custom options here if needed later +} + +async function request( // Generic type T for expected response data + method: string, + path: string, // Relative path (e.g., /v1/health) + data?: unknown, // Optional request body data + options: RequestOptions = {} // Optional fetch options (headers, etc.) +): Promise { + + if (!BASE_URL) { + // Or use SvelteKit's error helper for server-side/universal loads + // error(500, 'API Base URL is not configured.'); + throw new Error('API Base URL (VITE_API_BASE_URL) is not configured.'); + } + + // Construct the full URL, handling potential leading/trailing slashes + const cleanBase = BASE_URL.replace(/\/$/, ''); // Remove trailing slash from base + const cleanPath = path.replace(/^\//, ''); // Remove leading slash from path + const url = `${cleanBase}/${cleanPath}`; + + // Default headers + const headers = new Headers({ + Accept: 'application/json', + ...options.headers // Spread custom headers from options + }); + + // Fetch options + const fetchOptions: RequestInit = { + method: method.toUpperCase(), + headers, + ...options // Spread other custom options (credentials, mode, cache, etc.) + }; + + // Add body and Content-Type header if data is provided + if (data !== undefined && data !== null) { + headers.set('Content-Type', 'application/json'); + fetchOptions.body = JSON.stringify(data); + } + + // Add credentials option if needed for cookies/auth later + // fetchOptions.credentials = 'include'; + + try { + const response = await fetch(url, fetchOptions); + + // Check if the response is successful (status code 200-299) + if (!response.ok) { + let errorJson: unknown = null; + try { + // Try to parse error details from the response body + errorJson = await response.json(); + } catch (e) { + // Ignore if response body isn't valid JSON + console.warn('API Error response was not valid JSON.', response.status, response.statusText) + } + // Throw a custom error with status and potentially parsed error data + throw new ApiClientError( + `API request failed: ${response.status} ${response.statusText}`, + response.status, + errorJson + ); + } + + // Handle successful responses with no content (e.g., 204 No Content) + if (response.status === 204) { + // Type assertion needed because Promise expects a value, + // but 204 has no body. We return null. Adjust T if needed. + return null as T; + } + + // Parse successful JSON response + const responseData = await response.json(); + return responseData as T; // Assert the type based on the generic T + + } catch (err) { + // Handle network errors or errors thrown above + console.error(`API Client request error: ${method} ${path}`, err); + + // Re-throw the error so calling code can handle it + // If it's already our custom error, re-throw it directly + if (err instanceof ApiClientError) { + throw err; + } + // Otherwise, wrap network or other errors + throw new ApiClientError( + `Network or unexpected error during API request: ${err instanceof Error ? err.message : String(err)}`, + 0, // Use 0 or a specific code for network errors + err + ); + } +} + +// --- Helper Methods --- + +export const apiClient = { + get: (path: string, options: RequestOptions = {}): Promise => { + return request('GET', path, undefined, options); + }, + + post: (path: string, data: unknown, options: RequestOptions = {}): Promise => { + return request('POST', path, data, options); + }, + + put: (path: string, data: unknown, options: RequestOptions = {}): Promise => { + return request('PUT', path, data, options); + }, + + delete: (path: string, options: RequestOptions = {}): Promise => { + // Note: DELETE requests might have a body, but often don't. Adjust if needed. + return request('DELETE', path, undefined, options); + }, + + patch: (path: string, data: unknown, options: RequestOptions = {}): Promise => { + return request('PATCH', path, data, options); + } + // Can add other methods (HEAD, OPTIONS) if necessary +}; + +// Default export can sometimes be convenient, but named export is clear +// export default apiClient; \ No newline at end of file diff --git a/fe/src/lib/index.ts b/fe/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/fe/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/fe/src/lib/schemas/health.ts b/fe/src/lib/schemas/health.ts new file mode 100644 index 0000000..91496d8 --- /dev/null +++ b/fe/src/lib/schemas/health.ts @@ -0,0 +1,4 @@ +export interface HealthStatus { + status: string; + database: string; +} \ No newline at end of file diff --git a/fe/src/routes/+layout.svelte b/fe/src/routes/+layout.svelte new file mode 100644 index 0000000..2cfb68c --- /dev/null +++ b/fe/src/routes/+layout.svelte @@ -0,0 +1,41 @@ + + +
+ +
+
+

Shared Lists App

+ + +
+
+ + +
+ + +
+ + +
+

© {new Date().getFullYear()} Shared Lists App. All rights reserved.

+
+
+ + diff --git a/fe/src/routes/+page.svelte b/fe/src/routes/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/fe/src/service-worker.ts b/fe/src/service-worker.ts new file mode 100644 index 0000000..ce1565a --- /dev/null +++ b/fe/src/service-worker.ts @@ -0,0 +1,64 @@ +/// +// REMOVED: /// +/// + +// Import SvelteKit-provided variables ONLY +import { build, files, version } from '$service-worker'; + +declare let self: ServiceWorkerGlobalScope; +// Declare 'workbox' as any for now IF TypeScript still complains after removing @types/workbox-sw. +// Often, SvelteKit's types are enough, but this is a fallback. +declare const workbox: any; // Uncomment this line ONLY if 'Cannot find name workbox' persists + +console.log(`[Service Worker] Version: ${version}`); + +// --- Precaching --- +// Use the global workbox object (assuming SvelteKit injects it) +workbox.precaching.precacheAndRoute(build); +workbox.precaching.precacheAndRoute(files.map(f => ({ url: f, revision: null }))); + +// --- Runtime Caching --- +// Google Fonts +workbox.routing.registerRoute( + ({ url }) => url.origin === 'https://fonts.googleapis.com' || url.origin === 'https://fonts.gstatic.com', + new workbox.strategies.StaleWhileRevalidate({ + cacheName: 'google-fonts', + plugins: [ + new workbox.cacheableResponse.CacheableResponse({ statuses: [0, 200] }), + new workbox.expiration.ExpirationPlugin({ maxEntries: 20, maxAgeSeconds: 30 * 24 * 60 * 60 }), + ], + }), +); + +// Images from origin +workbox.routing.registerRoute( + ({ request, url }) => !!request && request.destination === 'image' && url.origin === self.location.origin, + new workbox.strategies.CacheFirst({ + cacheName: 'images', + plugins: [ + new workbox.cacheableResponse.CacheableResponse({ statuses: [0, 200] }), + new workbox.expiration.ExpirationPlugin({ + maxEntries: 50, + maxAgeSeconds: 30 * 24 * 60 * 60, + purgeOnQuotaError: true, + }), + ], + }), +); + +// --- Lifecycle --- +self.addEventListener('install', (event) => { + console.log('[Service Worker] Install event'); + // event.waitUntil(self.skipWaiting()); +}); + +self.addEventListener('activate', (event) => { + const extendableEvent = event as ExtendableEvent; + console.log('[Service Worker] Activate event'); + extendableEvent.waitUntil(workbox.precaching.cleanupOutdatedCaches()); + // event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('fetch', (event) => { + // console.log(`[Service Worker] Fetching: ${event.request.url}`); +}); \ No newline at end of file diff --git a/fe/static/favicon.png b/fe/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 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 literal 0 HcmV?d00001 diff --git a/fe/static/icon-192x192.png b/fe/static/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..53bc7dd185ca4dad7202042c1227de3e7662c2eb GIT binary patch literal 7314 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 literal 0 HcmV?d00001 diff --git a/fe/static/icon-512x512.png b/fe/static/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..55afb8628d27da857480388afcbb8276e10d92ea GIT binary patch literal 19015 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 FV1jXR#g_m8 literal 0 HcmV?d00001 diff --git a/fe/static/manifest.json b/fe/static/manifest.json new file mode 100644 index 0000000..c0d2678 --- /dev/null +++ b/fe/static/manifest.json @@ -0,0 +1,29 @@ +{ + "name": "Shared Household Lists", + "short_name": "SharedLists", + "description": "Collaborative shopping lists, OCR, and cost splitting for households.", + "start_url": "/", + "display": "standalone", + "background_color": "#f3f3f3", + "theme_color": "#c0377b", + "icons": [ + { + "src": "/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "any maskable" + } + ] +} \ No newline at end of file diff --git a/fe/svelte.config.js b/fe/svelte.config.js new file mode 100644 index 0000000..415a6c3 --- /dev/null +++ b/fe/svelte.config.js @@ -0,0 +1,9 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +const config = { + preprocess: vitePreprocess(), + kit: { adapter: adapter() } +}; + +export default config; diff --git a/fe/tsconfig.json b/fe/tsconfig.json new file mode 100644 index 0000000..0b2d886 --- /dev/null +++ b/fe/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/fe/vite.config.ts b/fe/vite.config.ts new file mode 100644 index 0000000..2d35c4f --- /dev/null +++ b/fe/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()] +});