diff --git a/.env.template b/.env.template index dcfbbc0..e9670ec 100644 --- a/.env.template +++ b/.env.template @@ -41,3 +41,7 @@ OAUTH2_REDIRECT_PATH=docs ## Hugging Face HF_TOKEN=your_access_token HF_HDR_DATASET_NAME=openmodelinitiative/hdr-submissions + +## Docker compose variables +NODE_ENV=development +UPLOAD_DIR=./uploads diff --git a/.gitignore b/.gitignore index 2031ecd..d9f135d 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ models_cache/ # Embedding models **/models--** + +# Local uploads +uploads/ diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 84269b5..5241688 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -54,16 +54,19 @@ To setup your environment run `task setup` Along with installing the necessary components, this will copy the `.env.template` setup into a `.env` file. -You can update this file if you'd like to change any of the default values for your development environment. +You can update this file if you'd like to change any of the default values for your development environment. You will at minimum need to provide a valid ID and SECRET for at least one authentication method. +You will also need to manually copy the `.env.template` file in `modules/odr_frontend` to a `.env` file in the same folder, and make matching updates. # Running The Local Environment -To start your virtual environment, run `task activate-venv` +To start your virtual environment, run `task activate-venv` or `source ./venv/bin/activate` -Then to start all the required services, run `task start-all` +Then to start all the required services, run `task dev` -This will spin up the required front end, api, and database components. +This will spin up the required front end, api, and database containers. + +Before the site will 'work' you will need to run `task data:migrate` to create the required data and tables By default, you will have the following: @@ -74,14 +77,23 @@ By default, you will have the following: - Interactive OpenAPI documentation at: http://localhost:31100/docs - Redoc documentation at: http://localhost:31100/redoc - A PGAdmin interface at: http://localhost:35050 + To connect to the database, open http://localhost:35050/browser/ + + Sign in with your PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD from your .env file + + On the left, right click servers, and click Register > Server + + Name the DB something like omi-database, then switch to the connection tab + + The host name/address will be 'postgres', the username will be your .env POSTGRES_USER, and the password will be your .env POSTGRES_PASSWORD # Running Tests Before committing any changes, ensure you run all tests to be sure existing behaviour continues to work. If any tests require updates to pass with your changes, ensure you update them as well. -To run all tests, run `task test-all` +To run all tests, run `task test-all`. Note this is not working at the moment and needs some work but some tests are run via the pipeline automatically. -(Todo: Add instructions for how to run the diffferent types individual as well to check work under development.) +(Todo: Add instructions for how to run the different types individual as well to check work under development.) # Committing Changes @@ -93,5 +105,3 @@ Before committing any changes, remember DCO and sign offs! If any commits are no To stop the environment, start by using ctrl+c (or cmd+c) to stop the current processes. Then use `task stop-all` to full stop the environment. - -If you want to remove the docker volumes used by the database, run `task db:teardown` diff --git a/Taskfile.yml b/Taskfile.yml index c404a43..305d34e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -4,7 +4,6 @@ version: '3' includes: api: ./modules/odr_api/Taskfile.api.yml - db: ./modules/odr_database/Taskfile.yml core: ./modules/odr_core/Taskfile.yml data: ./modules/odr_datamodel/Taskfile.yml frontend: ./modules/odr_frontend/Taskfile.yml @@ -34,7 +33,7 @@ tasks: platforms: [linux, darwin] - cmd: docker network inspect omi-network >/dev/null 2>&1 || docker network create omi-network - cmd: task setup-venv - - cmd: task frontend:install + - cmd: task frontend:install-dev setup-venv: desc: Setup the virtual environment @@ -74,7 +73,6 @@ tasks: - | task core:test & task core:test-db & - task db:test-postgres & task api-test:test-all & task monitoring:test & wait @@ -87,10 +85,9 @@ tasks: - docker compose logs -f smoke_tests: - desc: Run smoke tests for both API and PostgreSQL + desc: Run smoke tests for the API cmds: - task api-test:health-check - - task db:test-postgres build-all: desc: Build all services diff --git a/docker-compose.yml b/docker-compose.yml index 1dca30d..e6c939d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,11 +70,13 @@ services: - ./modules/odr_frontend/docker/entrypoint.sh:/app/docker/entrypoint.sh - odr_frontend_node_modules:/app/node_modules - odr_frontend_pnpm_store:/app/.pnpm-store + - ${UPLOAD_DIR:-./uploads}:/app/uploads ports: - "5173:5173" environment: - NODE_ENV: development + NODE_ENV: ${NODE_ENV:-development} API_SERVICE_URL: http://odr-api:31100/api/v1 + UPLOAD_DIR: /app/uploads env_file: - ./modules/odr_frontend/.env networks: diff --git a/modules/odr_api/odr_api/api/endpoints/image.py b/modules/odr_api/odr_api/api/endpoints/image.py index 4a1d4e1..5fe3c5b 100644 --- a/modules/odr_api/odr_api/api/endpoints/image.py +++ b/modules/odr_api/odr_api/api/endpoints/image.py @@ -14,6 +14,7 @@ from typing import Any import base64 import traceback +import json import subprocess import tempfile import os @@ -72,21 +73,6 @@ def calculate_entropy(tensor: torch.Tensor): # Helper functions for HDR metadata and preview conversion -def extract_metadata(image_bytes: bytes) -> Dict: - metadata = {} - try: - with Image.open(BytesIO(image_bytes)) as img: - exif_data = img.getexif() - if exif_data: - for tag_id, value in exif_data.items(): - tag = TAGS.get(tag_id, tag_id) - metadata[tag] = value - print('Metadata extracted from image file') - except UnidentifiedImageError: - print('Could not extract metadata from image') - return metadata - - def convert_ifd_rational(value): if isinstance(value, IFDRational): return float(value) @@ -95,16 +81,40 @@ def convert_ifd_rational(value): return value -def get_desired_metadata(metadata: Dict) -> Dict[str, Any]: - important_keys = ['Make', 'Model', 'BitsPerSample', 'BaselineExposure', 'LinearResponseLimit', 'ImageWidth', 'ImageLength', 'DateTime'] - result = {key: convert_ifd_rational(metadata.get(key)) for key in important_keys if key in metadata} +def get_metadata_with_exiftool(image_bytes: bytes) -> Dict[str, Any]: + with tempfile.NamedTemporaryFile(delete=False, suffix='.dng') as temp_input_file: + temp_input_file.write(image_bytes) + temp_input_filename = temp_input_file.name + + try: + tags = [ + '-Make', '-Model', '-BitsPerSample', '-BaselineExposure', + '-LinearResponseLimit', '-ImageWidth', '-ImageHeight', + '-DNGVersion' + ] + + result = subprocess.run( + ['exiftool', '-j'] + tags + [temp_input_filename], + capture_output=True, text=True, check=True + ) + + metadata = json.loads(result.stdout)[0] - if 'DNGVersion' in metadata: - dng_version = metadata['DNGVersion'] - version_string = '.'.join(str(b) for b in dng_version) - result['DNGVersion'] = version_string + if 'BitsPerSample' in metadata: + metadata['BitsPerSample'] = int(metadata['BitsPerSample']) + if 'BaselineExposure' in metadata: + metadata['BaselineExposure'] = float(metadata['BaselineExposure']) + if 'LinearResponseLimit' in metadata: + metadata['LinearResponseLimit'] = float(metadata['LinearResponseLimit']) + if 'ImageWidth' in metadata: + metadata['ImageWidth'] = int(metadata['ImageWidth']) + if 'ImageHeight' in metadata: + metadata['ImageHeight'] = int(metadata['ImageHeight']) - return result + return metadata + + finally: + os.remove(temp_input_filename) def convert_dng_to_jpg(dng_bytes: bytes) -> bytes: @@ -162,27 +172,19 @@ async def create_jpg_preview(file: UploadFile = File(...)): raise HTTPException(status_code=400, detail=str(e)) -# Endpoint for metadata retrieval @router.post("/image/metadata") async def get_image_metadata(file: UploadFile = File(...)): try: contents = await file.read() - metadata = extract_metadata(contents) + metadata = get_metadata_with_exiftool(contents) - important_metadata = get_desired_metadata(metadata) - - return important_metadata - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) + return metadata + except subprocess.CalledProcessError as e: + raise HTTPException(status_code=400, detail=f"Error processing image with exiftool: {str(e)}") + except json.JSONDecodeError as e: + raise HTTPException(status_code=500, detail=f"Error parsing exiftool output: {str(e)}") except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -# For debugging -# docker cp $(docker ps --filter name=omi-postgres-odr-api -q):./app/cleaned_image_exiftool.dng ./cleaned_image_exiftool.dng -# def save_image_locally(image_bytes: bytes, filename: str): -# with open(filename, 'wb') as f: -# f.write(image_bytes) + raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") def remove_metadata_with_exiftool(input_bytes): @@ -252,9 +254,6 @@ async def clean_image_metadata(file: UploadFile = File(...)): contents = await file.read() cleaned_image_bytes = remove_metadata_with_exiftool(contents) - # For debugging - # save_image_locally(cleaned_image_bytes, 'cleaned_image_exiftool.dng') - encoded_image = base64.b64encode(cleaned_image_bytes).decode('utf-8') return { diff --git a/modules/odr_api/requirements.txt b/modules/odr_api/requirements.txt index 088b46d..69d0743 100644 --- a/modules/odr_api/requirements.txt +++ b/modules/odr_api/requirements.txt @@ -1,6 +1,6 @@ uvicorn==0.30.5 fastapi==0.115.4 -pydantic[email]==2.8.2 +pydantic[email]==2.10.4 sqlalchemy==2.0.31 pytest==8.3.2 starlette==0.40.0 diff --git a/modules/odr_caption/requirements.txt b/modules/odr_caption/requirements.txt index 555f350..a88210a 100644 --- a/modules/odr_caption/requirements.txt +++ b/modules/odr_caption/requirements.txt @@ -4,7 +4,7 @@ qwen-vl-utils[decord] fastapi click openai -pydantic +pydantic==2.10.4 pillow python-dotenv torch diff --git a/modules/odr_core/requirements.txt b/modules/odr_core/requirements.txt index e197292..1f0ef1b 100644 --- a/modules/odr_core/requirements.txt +++ b/modules/odr_core/requirements.txt @@ -1,6 +1,6 @@ alembic==1.13.2 sqlalchemy==2.0.31 -pydantic==2.8.2 +pydantic==2.10.4 pydantic-settings==2.4.0 pytest==8.3.2 loguru==0.7.2 diff --git a/modules/odr_database/Taskfile.yml b/modules/odr_database/Taskfile.yml deleted file mode 100644 index e7ac45d..0000000 --- a/modules/odr_database/Taskfile.yml +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# ODR Database Taskfile -version: '3' - -vars: - COMPOSE_FILE: ${ROOT_DIR}/modules/odr_database/docker/postgres-compose.yml - POSTGRES_VOLUME_NAME: omi-postgres_postgres_data - PGADMIN_VOLUME_NAME: omi-postgres_pgadmin_data - -tasks: - -## PostgreSQL - setup-postgres: - desc: Setup the postgres database - cmds: - - cmd: ./scripts/setup.ps1 - platforms: [windows] - - cmd: ./scripts/setup.sh - platforms: [linux, darwin] - - start-postgres: - desc: Start the postgres database - cmds: - - > - docker compose -f {{.COMPOSE_FILE}} up - stop-postgres: - desc: Stop the postgres database - cmds: - - docker compose -f {{.COMPOSE_FILE}} down - rebuild-postgres: - desc: Rebuild the postgres database - cmds: - - > - docker compose -f {{.COMPOSE_FILE}} up --build - restart-postgres: - desc: Restart the postgres database - deps: - - stop-postgres - cmds: - - task: start-postgres - watch-postgres-logs: - desc: Watch the postgres logs - cmds: - - docker compose -f {{.COMPOSE_FILE}} logs -f - - reset-postgres: - desc: Reset the postgres database - cmds: - - docker compose -f {{.COMPOSE_FILE}} down - - docker volume rm {{.POSTGRES_VOLUME_NAME}} - - task: start-postgres - - task: watch-postgres-logs - - test-postgres: - desc: Test the postgres database - cmds: - - pytest -s --log-cli-level=INFO modules/odr_database/tests/test_db_connection.py - - teardown: - desc: Teardown the database and remove the volumes - cmds: - - docker compose -f {{.COMPOSE_FILE}} down - - docker volume rm {{.POSTGRES_VOLUME_NAME}} - - docker volume rm {{.PGADMIN_VOLUME_NAME}} diff --git a/modules/odr_database/tests/test_db_connection.py b/modules/odr_database/tests/test_db_connection.py deleted file mode 100644 index 0266dc9..0000000 --- a/modules/odr_database/tests/test_db_connection.py +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -import psycopg2 -import pytest -import os -import logging -from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -def test_db_connection(): - """ - Smoke test for PostgreSQL database connection. - - This test attempts to connect to the PostgreSQL database using environment - variables for configuration. It performs the following checks: - 1. Establishes a connection to the database - 2. Verifies the connection status - 3. Retrieves and prints the database version - - The test uses pytest for assertions and will fail if any step encounters an error. - - Environment Variables: - POSTGRES_DB: The name of the database to connect to - POSTGRES_USER: The username for database authentication - POSTGRES_PASSWORD: The password for database authentication - POSTGRES_HOST: The host address of the database (default: localhost) - POSTGRES_PORT: The port number for the database connection (default: 35432) - - Raises: - pytest.fail: If any database operation fails or assertions are not met - """ - connection = None - try: - # Log connection attempt - logger.info(f"Attempting to connect to database: {os.getenv('POSTGRES_DB')} " - f"on host: {os.getenv('POSTGRES_HOST', 'localhost')} " - f"port: {os.getenv('POSTGRES_PORT', '35432')}") - - # Attempt to connect - connection = psycopg2.connect( - dbname=os.getenv("POSTGRES_DB"), - user=os.getenv("POSTGRES_USER"), - password=os.getenv("POSTGRES_PASSWORD"), - host=os.getenv("POSTGRES_HOST", "localhost"), - port=os.getenv("POSTGRES_PORT", "35432") - ) - connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) - - # Check connection status - assert connection.status == psycopg2.extensions.STATUS_READY, "Database connection is not ready" - - # Perform a simple query to get version - with connection.cursor() as cursor: - cursor.execute("SELECT version();") - db_version = cursor.fetchone() - assert db_version, "Failed to fetch database version" - logger.info(f"Connected to PostgreSQL version: {db_version[0]}") - - logger.info("Database connection and operations successful") - - except (psycopg2.Error, AssertionError) as e: - logger.error(f"Database connection or operation failed: {str(e)}") - pytest.fail(f"Database connection or operation failed: {str(e)}") - finally: - if connection: - connection.close() - logger.info("Database connection closed") - - -if __name__ == "__main__": - test_db_connection() diff --git a/modules/odr_datamodel/alembic/versions/703075477888_add_content_source.py b/modules/odr_datamodel/alembic/versions/703075477888_add_content_source.py index 3848d28..3c7cfa2 100644 --- a/modules/odr_datamodel/alembic/versions/703075477888_add_content_source.py +++ b/modules/odr_datamodel/alembic/versions/703075477888_add_content_source.py @@ -22,11 +22,6 @@ depends_on: Union[str, Sequence[str], None] = None -class UserType(str, Enum): - USER = "user" - BOT = "bot" - - def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( @@ -61,8 +56,9 @@ def upgrade() -> None: op.drop_column("contents", "url") # Create the enum type - user_type_enum = Enum(UserType, name="usertype") - user_type_enum.create(op.get_bind(), checkfirst=True) + op.execute( + "CREATE TYPE usertype AS ENUM ('user', 'bot')" + ) # Update existing values to ensure they match the new enum op.execute( @@ -83,7 +79,9 @@ def upgrade() -> None: op.alter_column( "users", "user_type", - type_=user_type_enum, + type_=sa.Enum( + "user", "bot", name="usertype" + ), nullable=False, server_default=text("'user'::usertype"), ) diff --git a/modules/odr_frontend/template.env b/modules/odr_frontend/.env.template similarity index 55% rename from modules/odr_frontend/template.env rename to modules/odr_frontend/.env.template index 83b24e6..90e3b39 100644 --- a/modules/odr_frontend/template.env +++ b/modules/odr_frontend/.env.template @@ -1,6 +1,8 @@ PUBLIC_API_BASE_URL=http://localhost:31100 API_SERVICE_URL=http://odr-api:31100 + AUTH_SECRET=authsecret + GOOGLE_CLIENT_ID=google_client_id GOOGLE_CLIENT_SECRET=google_client_secret @@ -9,3 +11,15 @@ GITHUB_CLIENT_SECRET=github_client_secret DISCORD_CLIENT_ID=github_client_id DISCORD_CLIENT_SECRET=discord_client_secret + +## Postgres +POSTGRES_HOST=postgres +POSTGRES_DB=opendatarepository +POSTGRES_USER=opendatarepository +POSTGRES_PASSWORD=opendatarepository +POSTGRES_PORT=5432 +TEST_POSTGRES_DB=test_opendatarepository + +## Docker compose variables +NODE_ENV=development +UPLOAD_DIR=./uploads diff --git a/modules/odr_frontend/.gitignore b/modules/odr_frontend/.gitignore index 38bff89..3bdd087 100644 --- a/modules/odr_frontend/.gitignore +++ b/modules/odr_frontend/.gitignore @@ -13,8 +13,7 @@ Thumbs.db # Env .env .env.* -!.env.example -!.env.test +!.env.template # Vite vite.config.js.timestamp-* diff --git a/modules/odr_frontend/Taskfile.yml b/modules/odr_frontend/Taskfile.yml index a1d5764..35dc271 100644 --- a/modules/odr_frontend/Taskfile.yml +++ b/modules/odr_frontend/Taskfile.yml @@ -8,6 +8,10 @@ tasks: dir: '{{.ROOT_DIR}}/modules/odr_frontend' cmds: - pnpm install + install-dev: + desc: Install development dependencies + dir: '{{.ROOT_DIR}}/modules/odr_frontend' + cmds: - pnpm exec playwright install test: desc: Run unit and integration tests diff --git a/modules/odr_frontend/docker/Dockerfile.frontend.dev b/modules/odr_frontend/docker/Dockerfile.frontend.dev index 4f3e5ed..5cb021e 100644 --- a/modules/odr_frontend/docker/Dockerfile.frontend.dev +++ b/modules/odr_frontend/docker/Dockerfile.frontend.dev @@ -12,7 +12,7 @@ COPY package.json pnpm-lock.yaml* ./ COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -COPY template.env /app/.env +COPY .env.template /app/.env EXPOSE 5173 diff --git a/modules/odr_frontend/package.json b/modules/odr_frontend/package.json index e1842ce..453a3ff 100644 --- a/modules/odr_frontend/package.json +++ b/modules/odr_frontend/package.json @@ -51,9 +51,11 @@ "@auth/core": "^0.35.0", "@auth/pg-adapter": "^1.5.0", "@auth/sveltekit": "^1.5.0", + "@aws-sdk/client-s3": "^3.701.0", "@floating-ui/dom": "1.6.10", "highlight.js": "11.10.0", "next-auth": "^4.24.7", - "pg": "^8.13.0" + "pg": "^8.13.0", + "pnpm": "^9.14.4" } } diff --git a/modules/odr_frontend/pnpm-lock.yaml b/modules/odr_frontend/pnpm-lock.yaml index c9e9c43..4801a36 100644 --- a/modules/odr_frontend/pnpm-lock.yaml +++ b/modules/odr_frontend/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@auth/sveltekit': specifier: ^1.5.0 version: 1.5.0(@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.6(@types/node@22.2.0)))(svelte@4.2.19)(vite@5.4.6(@types/node@22.2.0)))(svelte@4.2.19) + '@aws-sdk/client-s3': + specifier: ^3.701.0 + version: 3.701.0 '@floating-ui/dom': specifier: 1.6.10 version: 1.6.10 @@ -30,6 +33,9 @@ importers: pg: specifier: ^8.13.0 version: 8.13.0 + pnpm: + specifier: ^9.14.4 + version: 9.14.4 devDependencies: '@playwright/test': specifier: 1.46.0 @@ -173,6 +179,169 @@ packages: nodemailer: optional: true + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.701.0': + resolution: {integrity: sha512-7iXmPC5r7YNjvwSsRbGq9oLVgfIWZesXtEYl908UqMmRj2sVAW/leLopDnbLT7TEedqlK0RasOZT05I0JTNdKw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-sso-oidc@3.699.0': + resolution: {integrity: sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.699.0 + + '@aws-sdk/client-sso@3.696.0': + resolution: {integrity: sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-sts@3.699.0': + resolution: {integrity: sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/core@3.696.0': + resolution: {integrity: sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-env@3.696.0': + resolution: {integrity: sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-http@3.696.0': + resolution: {integrity: sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-ini@3.699.0': + resolution: {integrity: sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.699.0 + + '@aws-sdk/credential-provider-node@3.699.0': + resolution: {integrity: sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-process@3.696.0': + resolution: {integrity: sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-sso@3.699.0': + resolution: {integrity: sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.696.0': + resolution: {integrity: sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.696.0 + + '@aws-sdk/middleware-bucket-endpoint@3.696.0': + resolution: {integrity: sha512-V07jishKHUS5heRNGFpCWCSTjRJyQLynS/ncUeE8ZYtG66StOOQWftTwDfFOSoXlIqrXgb4oT9atryzXq7Z4LQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-expect-continue@3.696.0': + resolution: {integrity: sha512-vpVukqY3U2pb+ULeX0shs6L0aadNep6kKzjme/MyulPjtUDJpD3AekHsXRrCCGLmOqSKqRgQn5zhV9pQhHsb6Q==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.701.0': + resolution: {integrity: sha512-adNaPCyTT+CiVM0ufDiO1Fe7nlRmJdI9Hcgj0M9S6zR7Dw70Ra5z8Lslkd7syAccYvZaqxLklGjPQH/7GNxwTA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-host-header@3.696.0': + resolution: {integrity: sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-location-constraint@3.696.0': + resolution: {integrity: sha512-FgH12OB0q+DtTrP2aiDBddDKwL4BPOrm7w3VV9BJrSdkqQCNBPz8S1lb0y5eVH4tBG+2j7gKPlOv1wde4jF/iw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-logger@3.696.0': + resolution: {integrity: sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.696.0': + resolution: {integrity: sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.696.0': + resolution: {integrity: sha512-M7fEiAiN7DBMHflzOFzh1I2MNSlLpbiH2ubs87bdRc2wZsDPSbs4l3v6h3WLhxoQK0bq6vcfroudrLBgvCuX3Q==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-ssec@3.696.0': + resolution: {integrity: sha512-w/d6O7AOZ7Pg3w2d3BxnX5RmGNWb5X4RNxF19rJqcgu/xqxxE/QwZTNd5a7eTsqLXAUIfbbR8hh0czVfC1pJLA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-user-agent@3.696.0': + resolution: {integrity: sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/region-config-resolver@3.696.0': + resolution: {integrity: sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.696.0': + resolution: {integrity: sha512-ijPkoLjXuPtgxAYlDoYls8UaG/VKigROn9ebbvPL/orEY5umedd3iZTcS9T+uAf4Ur3GELLxMQiERZpfDKaz3g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/token-providers@3.699.0': + resolution: {integrity: sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.699.0 + + '@aws-sdk/types@3.696.0': + resolution: {integrity: sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-arn-parser@3.693.0': + resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-endpoints@3.696.0': + resolution: {integrity: sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-locate-window@3.693.0': + resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-user-agent-browser@3.696.0': + resolution: {integrity: sha512-Z5rVNDdmPOe6ELoM5AhF/ja5tSjbe6ctSctDPb0JdDf4dT0v2MfwhJKzXju2RzX8Es/77Glh7MlaXLE0kCB9+Q==} + + '@aws-sdk/util-user-agent-node@3.696.0': + resolution: {integrity: sha512-KhKqcfyXIB0SCCt+qsu4eJjsfiOrNzK5dCV7RAW2YIpp+msxGUUX0NdRE9rkzjiv+3EMktgJm3eEIS+yxtlVdQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.696.0': + resolution: {integrity: sha512-dn1mX+EeqivoLYnY7p2qLrir0waPnCgS/0YdRCAVU2x14FgfUYCH6Im3w3oi2dMwhxfKY5lYVB5NKvZu7uI9lQ==} + engines: {node: '>=16.0.0'} + '@babel/runtime@7.25.6': resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} @@ -583,6 +752,209 @@ packages: peerDependencies: tailwindcss: '>=3.0.0' + '@smithy/abort-controller@3.1.8': + resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} + engines: {node: '>=16.0.0'} + + '@smithy/chunked-blob-reader-native@3.0.1': + resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} + + '@smithy/chunked-blob-reader@4.0.0': + resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} + + '@smithy/config-resolver@3.0.12': + resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} + engines: {node: '>=16.0.0'} + + '@smithy/core@2.5.4': + resolution: {integrity: sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==} + engines: {node: '>=16.0.0'} + + '@smithy/credential-provider-imds@3.2.7': + resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-codec@3.1.9': + resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} + + '@smithy/eventstream-serde-browser@3.0.13': + resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-config-resolver@3.0.10': + resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-node@3.0.12': + resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-universal@3.0.12': + resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} + engines: {node: '>=16.0.0'} + + '@smithy/fetch-http-handler@4.1.1': + resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} + + '@smithy/hash-blob-browser@3.1.9': + resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} + + '@smithy/hash-node@3.0.10': + resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} + engines: {node: '>=16.0.0'} + + '@smithy/hash-stream-node@3.1.9': + resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} + engines: {node: '>=16.0.0'} + + '@smithy/invalid-dependency@3.0.10': + resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@3.0.0': + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} + + '@smithy/md5-js@3.0.10': + resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} + + '@smithy/middleware-content-length@3.0.12': + resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-endpoint@3.2.4': + resolution: {integrity: sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-retry@3.0.28': + resolution: {integrity: sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-serde@3.0.10': + resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-stack@3.0.10': + resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} + engines: {node: '>=16.0.0'} + + '@smithy/node-config-provider@3.1.11': + resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} + engines: {node: '>=16.0.0'} + + '@smithy/node-http-handler@3.3.1': + resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} + engines: {node: '>=16.0.0'} + + '@smithy/property-provider@3.1.10': + resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} + engines: {node: '>=16.0.0'} + + '@smithy/protocol-http@4.1.7': + resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} + engines: {node: '>=16.0.0'} + + '@smithy/querystring-builder@3.0.10': + resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} + engines: {node: '>=16.0.0'} + + '@smithy/querystring-parser@3.0.10': + resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} + engines: {node: '>=16.0.0'} + + '@smithy/service-error-classification@3.0.10': + resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} + engines: {node: '>=16.0.0'} + + '@smithy/shared-ini-file-loader@3.1.11': + resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} + engines: {node: '>=16.0.0'} + + '@smithy/signature-v4@4.2.3': + resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} + engines: {node: '>=16.0.0'} + + '@smithy/smithy-client@3.4.5': + resolution: {integrity: sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==} + engines: {node: '>=16.0.0'} + + '@smithy/types@3.7.1': + resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} + engines: {node: '>=16.0.0'} + + '@smithy/url-parser@3.0.10': + resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} + + '@smithy/util-base64@3.0.0': + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-body-length-browser@3.0.0': + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} + + '@smithy/util-body-length-node@3.0.0': + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@3.0.0': + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-config-provider@3.0.0': + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-defaults-mode-browser@3.0.28': + resolution: {integrity: sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-defaults-mode-node@3.0.28': + resolution: {integrity: sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-endpoints@2.1.6': + resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-hex-encoding@3.0.0': + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-middleware@3.0.10': + resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} + engines: {node: '>=16.0.0'} + + '@smithy/util-retry@3.0.10': + resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-stream@3.3.1': + resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} + engines: {node: '>=16.0.0'} + + '@smithy/util-uri-escape@3.0.0': + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@3.0.0': + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-waiter@3.1.9': + resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} + engines: {node: '>=16.0.0'} + '@sodaru/yup-to-json-schema@2.0.1': resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} @@ -815,6 +1187,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1116,6 +1491,10 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + hasBin: true + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -1665,6 +2044,11 @@ packages: engines: {node: '>=18'} hasBin: true + pnpm@9.14.4: + resolution: {integrity: sha512-yBgLP75OS8oCyUI0cXiWtVKXQKbLrfGfp4JUJwQD6i8n1OHUagig9WyJtj3I6/0+5TMm2nICc3lOYgD88NGEqw==} + engines: {node: '>=18.12'} + hasBin: true + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1966,6 +2350,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -2130,6 +2517,9 @@ packages: tslib@2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2171,6 +2561,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + valibot@0.31.1: resolution: {integrity: sha512-2YYIhPrnVSz/gfT2/iXVTrSj92HwchCt9Cga/6hX4B26iCz9zkIsGTS0HjDYTZfTi1Un0X6aRvhBi1cfqs/i0Q==} @@ -2355,6 +2749,513 @@ snapshots: set-cookie-parser: 2.7.0 svelte: 4.2.19 + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.696.0 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.696.0 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-locate-window': 3.693.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-locate-window': 3.693.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.696.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.701.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.699.0(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/client-sts': 3.699.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/credential-provider-node': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/middleware-bucket-endpoint': 3.696.0 + '@aws-sdk/middleware-expect-continue': 3.696.0 + '@aws-sdk/middleware-flexible-checksums': 3.701.0 + '@aws-sdk/middleware-host-header': 3.696.0 + '@aws-sdk/middleware-location-constraint': 3.696.0 + '@aws-sdk/middleware-logger': 3.696.0 + '@aws-sdk/middleware-recursion-detection': 3.696.0 + '@aws-sdk/middleware-sdk-s3': 3.696.0 + '@aws-sdk/middleware-ssec': 3.696.0 + '@aws-sdk/middleware-user-agent': 3.696.0 + '@aws-sdk/region-config-resolver': 3.696.0 + '@aws-sdk/signature-v4-multi-region': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-endpoints': 3.696.0 + '@aws-sdk/util-user-agent-browser': 3.696.0 + '@aws-sdk/util-user-agent-node': 3.696.0 + '@aws-sdk/xml-builder': 3.696.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.4 + '@smithy/eventstream-serde-browser': 3.0.13 + '@smithy/eventstream-serde-config-resolver': 3.0.10 + '@smithy/eventstream-serde-node': 3.0.12 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-blob-browser': 3.1.9 + '@smithy/hash-node': 3.0.10 + '@smithy/hash-stream-node': 3.1.9 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/md5-js': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.4 + '@smithy/middleware-retry': 3.0.28 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.28 + '@smithy/util-defaults-mode-node': 3.0.28 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + '@smithy/util-waiter': 3.1.9 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0)': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.699.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/credential-provider-node': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/middleware-host-header': 3.696.0 + '@aws-sdk/middleware-logger': 3.696.0 + '@aws-sdk/middleware-recursion-detection': 3.696.0 + '@aws-sdk/middleware-user-agent': 3.696.0 + '@aws-sdk/region-config-resolver': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-endpoints': 3.696.0 + '@aws-sdk/util-user-agent-browser': 3.696.0 + '@aws-sdk/util-user-agent-node': 3.696.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.4 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.4 + '@smithy/middleware-retry': 3.0.28 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.28 + '@smithy/util-defaults-mode-node': 3.0.28 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.696.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/middleware-host-header': 3.696.0 + '@aws-sdk/middleware-logger': 3.696.0 + '@aws-sdk/middleware-recursion-detection': 3.696.0 + '@aws-sdk/middleware-user-agent': 3.696.0 + '@aws-sdk/region-config-resolver': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-endpoints': 3.696.0 + '@aws-sdk/util-user-agent-browser': 3.696.0 + '@aws-sdk/util-user-agent-node': 3.696.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.4 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.4 + '@smithy/middleware-retry': 3.0.28 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.28 + '@smithy/util-defaults-mode-node': 3.0.28 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.699.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.699.0(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/core': 3.696.0 + '@aws-sdk/credential-provider-node': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/middleware-host-header': 3.696.0 + '@aws-sdk/middleware-logger': 3.696.0 + '@aws-sdk/middleware-recursion-detection': 3.696.0 + '@aws-sdk/middleware-user-agent': 3.696.0 + '@aws-sdk/region-config-resolver': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-endpoints': 3.696.0 + '@aws-sdk/util-user-agent-browser': 3.696.0 + '@aws-sdk/util-user-agent-node': 3.696.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.4 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.4 + '@smithy/middleware-retry': 3.0.28 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.28 + '@smithy/util-defaults-mode-node': 3.0.28 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/core': 2.5.4 + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + fast-xml-parser: 4.4.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.696.0': + dependencies: + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.696.0': + dependencies: + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/node-http-handler': 3.3.1 + '@smithy/property-provider': 3.1.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/util-stream': 3.3.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0)': + dependencies: + '@aws-sdk/client-sts': 3.699.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/credential-provider-env': 3.696.0 + '@aws-sdk/credential-provider-http': 3.696.0 + '@aws-sdk/credential-provider-process': 3.696.0 + '@aws-sdk/credential-provider-sso': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0)) + '@aws-sdk/credential-provider-web-identity': 3.696.0(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/types': 3.696.0 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-node@3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.696.0 + '@aws-sdk/credential-provider-http': 3.696.0 + '@aws-sdk/credential-provider-ini': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/credential-provider-process': 3.696.0 + '@aws-sdk/credential-provider-sso': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0)) + '@aws-sdk/credential-provider-web-identity': 3.696.0(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/types': 3.696.0 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-process@3.696.0': + dependencies: + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))': + dependencies: + '@aws-sdk/client-sso': 3.696.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/token-providers': 3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0)) + '@aws-sdk/types': 3.696.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.696.0(@aws-sdk/client-sts@3.699.0)': + dependencies: + '@aws-sdk/client-sts': 3.699.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-arn-parser': 3.693.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.701.0': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/is-array-buffer': 3.0.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.696.0': + dependencies: + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-arn-parser': 3.693.0 + '@smithy/core': 2.5.4 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.696.0': + dependencies: + '@aws-sdk/core': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@aws-sdk/util-endpoints': 3.696.0 + '@smithy/core': 2.5.4 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/region-config-resolver@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.696.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.699.0(@aws-sdk/client-sso-oidc@3.699.0(@aws-sdk/client-sts@3.699.0))': + dependencies: + '@aws-sdk/client-sso-oidc': 3.699.0(@aws-sdk/client-sts@3.699.0) + '@aws-sdk/types': 3.696.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/types@3.696.0': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.693.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/types': 3.7.1 + '@smithy/util-endpoints': 2.1.6 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.693.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.696.0': + dependencies: + '@aws-sdk/types': 3.696.0 + '@smithy/types': 3.7.1 + bowser: 2.11.0 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.696.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.696.0 + '@aws-sdk/types': 3.696.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.696.0': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + '@babel/runtime@7.25.6': dependencies: regenerator-runtime: 0.14.1 @@ -2648,6 +3549,337 @@ snapshots: dependencies: tailwindcss: 3.4.10 + '@smithy/abort-controller@3.1.8': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@3.0.1': + dependencies: + '@smithy/util-base64': 3.0.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@4.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@3.0.12': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@smithy/core@2.5.4': + dependencies: + '@smithy/middleware-serde': 3.0.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@3.2.7': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + tslib: 2.8.1 + + '@smithy/eventstream-codec@3.1.9': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 3.7.1 + '@smithy/util-hex-encoding': 3.0.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@3.0.13': + dependencies: + '@smithy/eventstream-serde-universal': 3.0.12 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@3.0.12': + dependencies: + '@smithy/eventstream-serde-universal': 3.0.12 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@3.0.12': + dependencies: + '@smithy/eventstream-codec': 3.1.9 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@4.1.1': + dependencies: + '@smithy/protocol-http': 4.1.7 + '@smithy/querystring-builder': 3.0.10 + '@smithy/types': 3.7.1 + '@smithy/util-base64': 3.0.0 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@3.1.9': + dependencies: + '@smithy/chunked-blob-reader': 4.0.0 + '@smithy/chunked-blob-reader-native': 3.0.1 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/hash-node@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/hash-stream-node@3.1.9': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/middleware-content-length@3.0.12': + dependencies: + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@3.2.4': + dependencies: + '@smithy/core': 2.5.4 + '@smithy/middleware-serde': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@smithy/middleware-retry@3.0.28': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/service-error-classification': 3.0.10 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + tslib: 2.8.1 + uuid: 9.0.1 + + '@smithy/middleware-serde@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@3.1.11': + dependencies: + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@3.3.1': + dependencies: + '@smithy/abort-controller': 3.1.8 + '@smithy/protocol-http': 4.1.7 + '@smithy/querystring-builder': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/property-provider@3.1.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/protocol-http@4.1.7': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-uri-escape': 3.0.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/service-error-classification@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + + '@smithy/shared-ini-file-loader@3.1.11': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/signature-v4@4.2.3': + dependencies: + '@smithy/is-array-buffer': 3.0.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-uri-escape': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/smithy-client@3.4.5': + dependencies: + '@smithy/core': 2.5.4 + '@smithy/middleware-endpoint': 3.2.4 + '@smithy/middleware-stack': 3.0.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-stream': 3.3.1 + tslib: 2.8.1 + + '@smithy/types@3.7.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@3.0.10': + dependencies: + '@smithy/querystring-parser': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-base64@3.0.0': + dependencies: + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@3.0.0': + dependencies: + '@smithy/is-array-buffer': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@3.0.28': + dependencies: + '@smithy/property-provider': 3.1.10 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + bowser: 2.11.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@3.0.28': + dependencies: + '@smithy/config-resolver': 3.0.12 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/smithy-client': 3.4.5 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@2.1.6': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-retry@3.0.10': + dependencies: + '@smithy/service-error-classification': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-stream@3.3.1': + dependencies: + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/node-http-handler': 3.3.1 + '@smithy/types': 3.7.1 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@3.0.0': + dependencies: + '@smithy/util-buffer-from': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-waiter@3.1.9': + dependencies: + '@smithy/abort-controller': 3.1.8 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + '@sodaru/yup-to-json-schema@2.0.1': optional: true @@ -2702,7 +3934,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.4.0 + tslib: 2.8.1 '@tailwindcss/forms@0.5.7(tailwindcss@3.4.10)': dependencies: @@ -2938,6 +4170,8 @@ snapshots: binary-extensions@2.3.0: {} + bowser@2.11.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3271,6 +4505,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-xml-parser@4.4.1: + dependencies: + strnum: 1.0.5 + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -3767,6 +5005,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pnpm@9.14.4: {} + postcss-import@15.1.0(postcss@8.4.41): dependencies: postcss: 8.4.41 @@ -4048,6 +5288,8 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@1.0.5: {} + styled-jsx@5.1.1(react@18.3.1): dependencies: client-only: 0.0.1 @@ -4232,7 +5474,10 @@ snapshots: ts-interface-checker@0.1.13: {} - tslib@2.4.0: {} + tslib@2.4.0: + optional: true + + tslib@2.8.1: {} type-check@0.4.0: dependencies: @@ -4270,6 +5515,8 @@ snapshots: uuid@8.3.2: {} + uuid@9.0.1: {} + valibot@0.31.1: optional: true diff --git a/modules/odr_frontend/src/routes/+page.server.ts b/modules/odr_frontend/src/routes/+page.server.ts index 3b58f7d..893ab2d 100644 --- a/modules/odr_frontend/src/routes/+page.server.ts +++ b/modules/odr_frontend/src/routes/+page.server.ts @@ -1,7 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 -import { redirect } from '@sveltejs/kit'; +import { redirect, type RequestEvent } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { PG_API } from '$lib/server/pg'; +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; + +import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; export const load: PageServerLoad = async (event) => { const session = await event.locals.auth(); @@ -17,3 +21,145 @@ export const load: PageServerLoad = async (event) => { featureToggles: featureToggleMap }; }; + +const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'; +const PENDING_DIR = join(UPLOAD_DIR, 'pending'); +const ACCEPTED_DIR = join(UPLOAD_DIR, 'accepted'); +const REJECTED_DIR = join(UPLOAD_DIR, 'rejected'); +const API_BASE_URL = process.env.API_SERVICE_URL || 'http://odr-api:31100/api/v1'; + +let s3Client: S3Client | null = null; + +if (process.env.AWS_REGION && process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) { + s3Client = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, + }); +} else { + console.warn('AWS credentials not found in environment variables. S3 functionality will be disabled.'); +} + +async function makeApiCall(endpoint: string, file: Blob, filename: string) { + const formData = new FormData(); + formData.append('file', file, filename); + + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error(`Error calling ${endpoint}:`, error); + throw new Error(`API error (${endpoint}): ${error instanceof Error ? error.message : String(error)}`); + } +} + +export const actions = { + upload: async ({ request }: RequestEvent) => { + const formData = await request.formData(); + const file = formData.get('file'); + const userId = formData.get('userId'); + + if (!(file instanceof Blob)) { + return { success: false, error: 'No file uploaded' }; + } + + if (!userId || typeof userId !== 'string') { + return { success: false, error: 'No user ID provided' }; + } + + try { + const originalFilename = file.name; + const fileExtension = originalFilename.split('.').pop(); + const timestamp = new Date().toISOString().replace(/[-:.]/g, ''); + const uniqueFileName = `${userId}_${timestamp}.${fileExtension}`; + + // Clean metadata + const cleanedData = await makeApiCall('/image/clean-metadata', file, uniqueFileName); + const cleaned_image_base64 = cleanedData.cleaned_image + + // Convert base64 to Blob + const byteCharacters = atob(cleaned_image_base64); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const cleanedBlob = new Blob([byteArray], { type: 'application/octet-stream' }); + + const cleanedBuffer = Buffer.from(byteArray); + + // Calculate HDR stats and metadata + const statsData = await makeApiCall('/image/hdr-stats', cleanedBlob, uniqueFileName); + const metadataData = await makeApiCall('/image/metadata', cleanedBlob, uniqueFileName); + + // Create JPG preview + const jpgData = await makeApiCall('/image/jpg-preview', cleanedBlob, uniqueFileName); + const jpgBuffer = Buffer.from(jpgData.jpg_preview, 'base64'); + const jpgFileName = `${userId}_${timestamp}.jpg` + + // Combine metadata for temporary file storage + const metadataContent = JSON.stringify({ + uploadedByUser: userId, + hdrStats: statsData, + metadata: metadataData + }, null, 2); + const metadataFileName = `${userId}_${timestamp}.json` + + await mkdir(PENDING_DIR, { recursive: true }); + await mkdir(ACCEPTED_DIR, { recursive: true }); + await mkdir(REJECTED_DIR, { recursive: true }); + + if (process.env.NODE_ENV === 'production') { + // S3 upload for production + if (!s3Client) { + throw new Error('S3 client is not initialized'); + } + + await s3Client.send(new PutObjectCommand({ + Bucket: process.env.S3_BUCKET_NAME, + Key: uniqueFileName, + Body: cleanedBuffer, + })); + + await s3Client.send(new PutObjectCommand({ + Bucket: process.env.S3_BUCKET_NAME, + Key: jpgFileName, + Body: jpgBuffer, + })); + + await s3Client.send(new PutObjectCommand({ + Bucket: process.env.S3_BUCKET_NAME, + Key: metadataFileName, + Body: metadataContent, + })); + + console.log(`File uploaded to S3: ${uniqueFileName}`); + } else { + // Local file system for development + const cleanedFilePath = join(PENDING_DIR, uniqueFileName); + await writeFile(cleanedFilePath, cleanedBuffer); + + const jpgFilePath = join(PENDING_DIR, jpgFileName); + await writeFile(jpgFilePath, jpgBuffer); + + const metadataFilePath = join(PENDING_DIR, metadataFileName); + await writeFile(metadataFilePath, metadataContent); + } + + return { success: true, uniqueFileName }; + } catch (error) { + console.error('Error uploading file:', error); + return { success: false, error: 'Failed to upload file' }; + } + } +}; diff --git a/modules/odr_frontend/src/routes/+page.svelte b/modules/odr_frontend/src/routes/+page.svelte index 7029084..76869cd 100644 --- a/modules/odr_frontend/src/routes/+page.svelte +++ b/modules/odr_frontend/src/routes/+page.svelte @@ -5,6 +5,77 @@ $: featureToggles = $page.data.featureToggles; $: user = $page.data.session?.user; + + const acceptedFileTypes:Array = ['.dng']; + let uploadStatus:string = ''; + let uploadProgress: number = 0; + let files: FileList; + + let errorArray: string[] = []; + + $: selectedFiles = files ? Array.from(files) : []; + + async function uploadFiles() { + const totalFiles = selectedFiles.length; + let completedUploads = 0; + + for (const file of selectedFiles) { + if (acceptedFileTypes.some(type => file.name.endsWith(type))) { + uploadStatus = `Uploading ${file.name}...`; + + const formData = new FormData(); + formData.append('file', file); + formData.append('userId', user?.id ?? ''); + + try { + const response = await fetch('?/upload', { + method: 'POST', + body: formData + }); + + if (response.ok) { + const result = await response.json(); + if (result.type === 'success') { + const data = JSON.parse(result.data); + const isSuccess = data[1]; + const message = data[2]; + + if (isSuccess) { + uploadStatus = `${file.name} uploaded successfully`; + } else { + errorArray.push(`Failed to upload ${file.name}: ${message}`); + } + } else { + errorArray.push(`Failed to upload ${file.name}: Unexpected response type`); + } + } else { + errorArray.push(`Failed to upload ${file.name}: ${response.statusText || 'Unknown error'}`); + } + } catch (error: unknown) { + console.error('Error uploading file:', error); + if (error instanceof Error) { + errorArray.push(`Error uploading ${file.name}: ${error.message}`); + } else { + errorArray.push(`Error uploading ${file.name}: Unknown error`); + } + } + + completedUploads++; + uploadProgress = Math.round((completedUploads / totalFiles) * 100); + + } else { + errorArray.push(`Invalid file type: ${file.name}`); + } + } + + if (errorArray.length === 0) { + uploadStatus = 'All files uploaded successfully'; + } else { + uploadStatus = `Uploaded with ${errorArray.length} error${errorArray.length > 1 ? 's' : ''}`; + } + + files = new DataTransfer().files + } @@ -12,25 +83,65 @@
-
+
{#if featureToggles['HDR Image Upload']} - - -
- -
-
- Upload Images - Currently only accepting RAW images in .DNG -
+
+ + +
+ +
+
+ + {selectedFiles.length > 0 ? `${selectedFiles.length} file(s) selected` : 'Upload Images'} + + Currently only accepting RAW images in .DNG +
+
+ +
+ {#if uploadStatus} +
+
+
+
+

{uploadStatus}

+
+ {#if uploadProgress > 0 && uploadProgress < 100} + +

{uploadProgress}% complete

+ {/if} + {#if errorArray.length > 0} +
+

Errors:

+
    + {#each errorArray as error} +
  • {error}
  • + {/each} +
+
+ {/if} +
+
+
+ {/if} +
{/if} -
+ {#if featureToggles['Show Datasets']}
diff --git a/modules/odr_frontend/src/routes/admin/+layout.svelte b/modules/odr_frontend/src/routes/admin/+layout.svelte index 99b6894..f254559 100644 --- a/modules/odr_frontend/src/routes/admin/+layout.svelte +++ b/modules/odr_frontend/src/routes/admin/+layout.svelte @@ -16,6 +16,11 @@ Feature Toggles +
  • + + Image Moderation + +
  • diff --git a/modules/odr_frontend/src/routes/admin/moderation/+page.server.ts b/modules/odr_frontend/src/routes/admin/moderation/+page.server.ts new file mode 100644 index 0000000..5cb8c87 --- /dev/null +++ b/modules/odr_frontend/src/routes/admin/moderation/+page.server.ts @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: Apache-2.0 +import { readdir, readFile, rename } from 'fs/promises'; +import { join } from 'path'; +import type { PageServerLoad } from './$types'; +import { error } from '@sveltejs/kit'; + +import { S3Client, ListObjectsV2Command, GetObjectCommand, CopyObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; + +import { PG_API } from '$lib/server/pg'; + +const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'; +const PENDING_DIR = join(UPLOAD_DIR, 'pending'); +const ACCEPTED_DIR = join(UPLOAD_DIR, 'accepted'); +const REJECTED_DIR = join(UPLOAD_DIR, 'rejected'); +const ITEMS_PER_PAGE = 10; + +let s3Client: S3Client | null = null; + +if (process.env.AWS_REGION && process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) { + s3Client = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, + }); +} else { + console.warn('AWS credentials not found in environment variables. S3 functionality will be disabled.'); +} + +const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME; + +async function getS3Objects(prefix: string) { + if (!s3Client) { + throw new Error('S3 client is not initialized'); + } + + const command = new ListObjectsV2Command({ + Bucket: S3_BUCKET_NAME, + Prefix: prefix, + }); + const response = await s3Client.send(command); + return response.Contents || []; +} + +async function getS3ObjectContent(key: string) { + if (!s3Client) { + throw new Error('S3 client is not initialized'); + } + + const command = new GetObjectCommand({ + Bucket: S3_BUCKET_NAME, + Key: key, + }); + const response = await s3Client.send(command); + return response.Body?.transformToString(); +} + + +interface ImageData { + filename: string; + previewUrl: string; + metadata: { + uploadedByUser: string; + hdrStats: any; + metadata: any; + }; +} + +export const load: PageServerLoad = async ({ url }) => { + const page = Number(url.searchParams.get('page')) || 1; + const users = await PG_API.users.getAll(); + + let imageFiles: string[]; + let totalPages: number; + let imagesData: ImageData[]; + + if (process.env.NODE_ENV === 'production') { + // S3 logic + const s3Objects = await getS3Objects('pending/'); + imageFiles = s3Objects + .map(obj => obj.Key) + .filter((key): key is string => key !== undefined && key.endsWith('.jpg')); + totalPages = Math.ceil(imageFiles.length / ITEMS_PER_PAGE); + + const startIndex = (page - 1) * ITEMS_PER_PAGE; + const endIndex = startIndex + ITEMS_PER_PAGE; + const pageImageFiles = imageFiles.slice(startIndex, endIndex); + + imagesData = await Promise.all( + pageImageFiles.map(async (filename) => { + const jsonFilename = filename.replace('.jpg', '.json'); + const content = await getS3ObjectContent(jsonFilename); + + if (content === undefined) { + throw new Error(`Failed to retrieve content for ${jsonFilename}`); + } + + const metadata = JSON.parse(content); + + const user = users.find(u => u.id === parseInt(metadata.uploadedByUser)); + + return { + filename, + previewUrl: `https://${S3_BUCKET_NAME}.s3.amazonaws.com/${filename}`, + metadata: { + ...metadata, + uploadedByUser: user ? user.email : metadata.uploadedByUser + }, + }; + }) + ); + + console.log(`Grabbed image data from S3`); + } else { + // Local file system logic + const files = await readdir(PENDING_DIR); + imageFiles = files.filter(file => file.endsWith('.jpg')); + totalPages = Math.ceil(imageFiles.length / ITEMS_PER_PAGE); + + const startIndex = (page - 1) * ITEMS_PER_PAGE; + const endIndex = startIndex + ITEMS_PER_PAGE; + const pageImageFiles = imageFiles.slice(startIndex, endIndex); + + imagesData = await Promise.all( + pageImageFiles.map(async (filename) => { + const jsonFilename = filename.replace('.jpg', '.json'); + const jsonPath = join(PENDING_DIR, jsonFilename); + const metadata = JSON.parse(await readFile(jsonPath, 'utf-8')); + + const user = users.find(u => u.id === parseInt(metadata.uploadedByUser)); + + return { + filename, + previewUrl: `/uploads/pending/${filename}`, + metadata: { + ...metadata, + uploadedByUser: user ? user.email : metadata.uploadedByUser + }, + }; + }) + ); + } + + return { + images: imagesData, + currentPage: page, + totalPages, + }; +}; + +export const actions = { + accept: async ({ request }) => { + const formData = await request.formData(); + const filename = formData.get('filename'); + if (typeof filename !== 'string') { + throw error(400, 'Invalid filename'); + } + await moveFile(filename, 'accepted'); + return { success: true }; + }, + + reject: async ({ request }) => { + const formData = await request.formData(); + const filename = formData.get('filename'); + if (typeof filename !== 'string') { + throw error(400, 'Invalid filename'); + } + await moveFile(filename, 'rejected'); + return { success: true }; + } + }; + + async function moveFile(filename: string, destFolder: string) { + if (process.env.NODE_ENV === 'production') { + // S3 move logic + if (!s3Client) { + throw new Error('S3 client is not initialized'); + } + + const sourceKey = `pending/${filename}`; + const destKey = `${destFolder}/${filename}`; + + const fileExtensions = ['jpg', 'json', 'dng']; + + await Promise.all(fileExtensions.map(async (ext) => { + const sourceKeyExt = sourceKey.replace(/\.[^.]+$/, `.${ext}`); + const destKeyExt = destKey.replace(/\.[^.]+$/, `.${ext}`); + + await s3Client.send(new CopyObjectCommand({ + Bucket: S3_BUCKET_NAME, + CopySource: `${S3_BUCKET_NAME}/${sourceKeyExt}`, + Key: destKeyExt, + })); + + await s3Client.send(new DeleteObjectCommand({ + Bucket: S3_BUCKET_NAME, + Key: sourceKeyExt, + })); + })); + } else { + // Local file system move logic + const sourcePath = join(PENDING_DIR, filename); + const destPath = join(destFolder === 'accepted' ? ACCEPTED_DIR : REJECTED_DIR, filename); + await rename(sourcePath, destPath); + + // Move associated JSON and DNG files + ['json', 'dng'].forEach(async (ext) => { + const sourcePathExt = sourcePath.replace('.jpg', `.${ext}`); + const destPathExt = destPath.replace('.jpg', `.${ext}`); + await rename(sourcePathExt, destPathExt); + }); + } + } diff --git a/modules/odr_frontend/src/routes/admin/moderation/+page.svelte b/modules/odr_frontend/src/routes/admin/moderation/+page.svelte new file mode 100644 index 0000000..19ea623 --- /dev/null +++ b/modules/odr_frontend/src/routes/admin/moderation/+page.svelte @@ -0,0 +1,116 @@ + + +
    +

    HDR Image Moderation

    +
    + + + + + + + + + + + {#each images as image} + + + + + + + {/each} + +
    ImageUserMetadataAction
    + + {image.metadata.uploadedByUser} +
    {JSON.stringify(image.metadata, null, 2)}
    +
    + + +
    +
    + +{#if showPreview} + +{/if} + + + +
    diff --git a/modules/odr_frontend/src/routes/admin/moderation/moderation.css b/modules/odr_frontend/src/routes/admin/moderation/moderation.css new file mode 100644 index 0000000..1160f11 --- /dev/null +++ b/modules/odr_frontend/src/routes/admin/moderation/moderation.css @@ -0,0 +1,90 @@ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + } + + .modal-content { + max-width: 90vw; + max-height: 90vh; + display: flex; + justify-content: center; + align-items: center; + } + + .modal-content img { + max-width: 90vw; + max-height: 90vh; + object-fit: contain; + } + + .close-button { + position: absolute; + top: 20px; + right: 20px; + width: 30px; + height: 30px; + background-color: rgba(255, 255, 255, 0.7); + border: none; + border-radius: 50%; + font-size: 20px; + line-height: 1; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + padding: 0; + color: #333; + } + + .close-button:hover, + .close-button:focus { + background-color: rgba(255, 255, 255, 0.9); + outline: none; + } + + .image-preview-button { + background: none; + border: none; + padding: 0; + cursor: pointer; + } + + .image-preview-button:focus { + outline: 2px solid blue; + } + + .preview-image { + max-width: 100px; + max-height: 100px; + object-fit: cover; + } + + .moderation-page { + height: calc(100vh - 128px); + overflow: auto; + display: flex; + flex-direction: column; + } + + .table-container { + flex: 1; + overflow: auto; + margin-bottom: 1rem; + } + + .pagination { + margin-top: auto; + padding: 1rem 0; + } + + h1 { + padding: 0 0 1rem 0; + } diff --git a/modules/odr_frontend/vite.config.ts b/modules/odr_frontend/vite.config.ts index 50659aa..c49e3bb 100644 --- a/modules/odr_frontend/vite.config.ts +++ b/modules/odr_frontend/vite.config.ts @@ -4,5 +4,10 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit(), purgeCss()] + plugins: [sveltekit(), purgeCss()], + server: { + fs: { + allow: ['/app/uploads'] + } + } }); diff --git a/modules/odr_monitoring/requirements.txt b/modules/odr_monitoring/requirements.txt index 0a9f2e4..e18ac97 100644 --- a/modules/odr_monitoring/requirements.txt +++ b/modules/odr_monitoring/requirements.txt @@ -1,4 +1,4 @@ -pydantic==2.8.2 +pydantic==2.10.4 black==24.3.0 flake8==7.1.1 pytest==8.3.2 diff --git a/scripts/setup_virt_env.ps1 b/scripts/setup_virt_env.ps1 index 1282cdb..31dcdc9 100755 --- a/scripts/setup_virt_env.ps1 +++ b/scripts/setup_virt_env.ps1 @@ -21,10 +21,10 @@ if (Test-Path -Path ".\venv") { # Upgrade pip and install requirements python -m pip install --upgrade pip -pip install -e ./modules/odr_core -pip install -e ./modules/odr_api -pip install -e ./modules/odr_monitoring -pip install -e ./modules/odr_caption +# pip install -e ./modules/odr_core +# pip install -e ./modules/odr_api +# pip install -e ./modules/odr_monitoring +# pip install -e ./modules/odr_caption pip install -r ./requirements-dev.txt -pip install git+https://github.com/dottxt-ai/outlines.git --upgrade +# pip install git+https://github.com/dottxt-ai/outlines.git --upgrade pre-commit install diff --git a/scripts/setup_virt_env.sh b/scripts/setup_virt_env.sh index e7bd24d..6d0d804 100755 --- a/scripts/setup_virt_env.sh +++ b/scripts/setup_virt_env.sh @@ -3,9 +3,10 @@ python3 -m venv venv source venv/bin/activate pip install --upgrade pip -pip install -e ./modules/odr_core -pip install -e ./modules/odr_api -pip install -e ./modules/odr_caption +# pip install -e ./modules/odr_core +# pip install -e ./modules/odr_api +# pip install -e ./modules/odr_monitoring +# pip install -e ./modules/odr_caption pip install -r ./requirements-dev.txt -pip install git+https://github.com/dottxt-ai/outlines.git --upgrade +# pip install git+https://github.com/dottxt-ai/outlines.git --upgrade pre-commit install