diff --git a/.env.ci.test b/.env.ci similarity index 51% rename from .env.ci.test rename to .env.ci index ab52aac3..d0659933 100644 --- a/.env.ci.test +++ b/.env.ci @@ -3,6 +3,15 @@ APP_ENV=development APP_KEY= APP_DEBUG=true APP_URL=http://localhost:80 + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US +APP_MAINTENANCE_DRIVER=file +APP_MAINTENANCE_STORE=database +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 APP_DESCRIPTION='nmrXiv is currently developed as the FAIR, consensus-driven NMR data repository and computational platform. The ultimate goal is to accelerate broader coordination and data sharing among natural product (NP) researchers by enabling the storage, management, sharing and analysis of NMR data.' COOL_OFF_PERIOD=10 SCHEMA_VERSION=beta @@ -11,19 +20,22 @@ SHOW_BANNER=true GITHUB_LICENSE_URL=https://api.github.com/licenses EUROPEMC_WS_API=https://www.ebi.ac.uk/europepmc/webservices/rest/search -ORCID_ID_SEARCH_API=https://pub.orcid.org/v2.1/search -ORCID_ID_EMPLOYMENT_API=https://pub.orcid.org/v3.0/{orcid_id}/employments -ORCID_ID_PERSON_API=https://pub.orcid.org/v3.0/{orcid_id}/person +ORCID_BASE_URL=https://pub.orcid.org/v3.0 +CM_API=https://api.naturalproducts.net/latest/ +CROSSREF_API=https://api.crossref.org/works/ +DATACITE_API=https://api.datacite.org/ +DATACITE_TEST_API=https://api.test.datacite.org LOG_CHANNEL=stack +LOG_STACK=single LOG_LEVEL=debug DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 -DB_DATABASE=nmrxiv +DB_DATABASE=nmrxiv_test DB_USERNAME=postgres -DB_PASSWORD=postgres +DB_PASSWORD=password BROADCAST_CONNECTION=log CACHE_STORE=file @@ -31,6 +43,9 @@ FILESYSTEM_DRIVER=local QUEUE_CONNECTION=redis SESSION_DRIVER=redis SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null MEMCACHED_HOST=memcached @@ -58,6 +73,7 @@ AWS_BUCKET=nmrxiv AWS_BUCKET_PUBLIC=nmrxiv-public AWS_ENDPOINT=https://s3.uni-jena.de AWS_URL=https://s3.uni-jena.de +AWS_USE_PATH_STYLE_ENDPOINT=false PUSHER_APP_ID= PUSHER_APP_KEY= @@ -67,9 +83,9 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" -SCOUT_DRIVER=meilisearch -SCOUT_PREFIX=dev_ -MEILISEARCH_HOST=https://msdev.nmrxiv.org +SCOUT_DRIVER=null +SCOUT_PREFIX=test_ +MEILISEARCH_HOST=http://localhost:7700/ MEILISEARCH_KEY= MEILISEARCH_PUBLICKEY= @@ -81,4 +97,40 @@ TWITTER_CLIENT_ID= TWITTER_CLIENT_SECRET= TWITTER_REDIRECT_URL=http://localhost:80/auth/login/twitter/callback +ORCID_CLIENT_ID= +ORCID_CLIENT_SECRET= +ORCID_REDIRECT_URL=http://localhost/auth/login/orcid/callback +ORCID_ENVIRONMENT=sandbox + +NFDIAAI_CLIENT_ID= +NFDIAAI_CLIENT_SECRET= +NFDIAAI_REDIRECT_URL="${APP_URL}/auth/login/regapp/callback" + TELESCOPE_ENABLED=false + +#DATACITE Properties +DOI_HOST=datacite +DATACITE_USERNAME= +DATACITE_SECRET= +DATACITE_PREFIX= +DATACITE_ENDPOINT=https://api.test.datacite.org + +NMRKIT_URL=https://nodejs.nmrxiv.org +PUBCHEM_URL=https://pubchem.ncbi.nlm.nih.gov +COMMON_CHEMISTRY_URL=https://commonchemistry.cas.org/api +CAS_API_TOKEN= +CHEMISTRY_STANDARDIZE_URL=https://api.naturalproducts.net/latest/chem/standardize + +BACKUP_KEEP_DAYS=7 + +# CSP Configuration +CSP_ENABLED=true +CSP_REPORT_URI="/csp-violation-report" +CSP_NONCE_ENABLED=true +CSP_ENABLED_WHILE_HOT_RELOADING=false + +# Additional CSP sources (comma-separated, no spaces) +# CSP_ADDITIONAL_CONNECT_SRC="https://api.example.com,https://analytics.example.com" +# CSP_ADDITIONAL_IMG_SRC="https://cdn.example.com,https://images.example.com" +# CSP_ADDITIONAL_SCRIPT_SRC="https://cdn.example.com" +# CSP_ADDITIONAL_STYLE_SRC="https://fonts.example.com" \ No newline at end of file diff --git a/.env.example b/.env.example index 9c88138b..96780ed0 100644 --- a/.env.example +++ b/.env.example @@ -1,37 +1,50 @@ +# ============================================================================ +# Application Configuration +# ============================================================================ APP_NAME=Laravel APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=localhost:80 +APP_DESCRIPTION='nmrXiv is currently developed as the FAIR, consensus-driven NMR data repository and computational platform. The ultimate goal is to accelerate broader coordination and data sharing among natural product (NP) researchers by enabling the storage, management, sharing and analysis of NMR data.' +# Localization APP_LOCALE=en APP_FALLBACK_LOCALE=en APP_FAKER_LOCALE=en_US + +# Maintenance & Workers APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_STORE=database PHP_CLI_SERVER_WORKERS=4 +# Application Specific BCRYPT_ROUNDS=12 -APP_DESCRIPTION='nmrXiv is currently developed as the FAIR, consensus-driven NMR data repository and computational platform. The ultimate goal is to accelerate broader coordination and data sharing among natural product (NP) researchers by enabling the storage, management, sharing and analysis of NMR data.' COOL_OFF_PERIOD=10 SCHEMA_VERSION=beta - -SHOW_BANNER=true - -GITHUB_LICENSE_URL=https://api.github.com/licenses -EUROPEMC_WS_API=https://www.ebi.ac.uk/europepmc/webservices/rest/search -ORCID_ID_SEARCH_API=https://pub.orcid.org/v2.1/search -ORCID_ID_EMPLOYMENT_API=https://pub.orcid.org/v3.0/{orcid_id}/employments -ORCID_ID_PERSON_API=https://pub.orcid.org/v3.0/{orcid_id}/person -CM_API=https://api.cheminf.studio/latest/ -CROSSREF_API=https://api.crossref.org/works/ -DATACITE_API=https://api.datacite.org/ - - +BACKUP_KEEP_DAYS=7 + +# Spectra Parsing Configuration +NMRKIT_API_URL=https://nmrkit.nmrxiv.org/latest/spectra/parse/url +BIOSCHEMA_API_URL=https://nmrxiv.org/api/v1/schemas/bioschemas +SPECTRA_STORAGE_DISK=local +SPECTRA_STORAGE_PATH=spectra_parse +SPECTRA_JOB_TRIES=3 +SPECTRA_JOB_TIMEOUT=600 +SPECTRA_RETRY_COUNT=3 +SPECTRA_DOWNLOAD_TIMEOUT=300 +SPECTRA_API_TIMEOUT=300 + +# ============================================================================ +# Logging Configuration +# ============================================================================ LOG_CHANNEL=stack LOG_STACK=single LOG_LEVEL=debug +# ============================================================================ +# Database Configuration +# ============================================================================ DB_CONNECTION=pgsql DB_HOST=pgsql DB_PORT=5432 @@ -39,23 +52,35 @@ DB_DATABASE=nmrxiv DB_USERNAME=sail DB_PASSWORD=password +# ============================================================================ +# Cache, Queue, Session & Broadcasting +# ============================================================================ BROADCAST_CONNECTION=log CACHE_STORE=file -FILESYSTEM_DRIVER=local QUEUE_CONNECTION=redis + SESSION_DRIVER=redis SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ SESSION_DOMAIN=null +# ============================================================================ +# Memcached Configuration +# ============================================================================ MEMCACHED_HOST=memcached +# ============================================================================ +# Redis Configuration +# ============================================================================ REDIS_CLIENT=predis REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379 +# ============================================================================ +# Mail Configuration +# ============================================================================ MAIL_MAILER=smtp MAIL_HOST=mailpit MAIL_PORT=1025 @@ -65,18 +90,25 @@ MAIL_SCHEME=null MAIL_FROM_ADDRESS=info.nmrxiv@uni-jena.de MAIL_FROM_NAME="${APP_NAME}" +# ============================================================================ +# Filesystem Configuration +# ============================================================================ FILESYSTEM_DRIVER=minio FILESYSTEM_DRIVER_PUBLIC=minio_public +# AWS S3 Compatible Storage (MinIO) AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET=nmrxiv +AWS_BUCKET_PUBLIC=nmrxiv-public AWS_ENDPOINT=http://localhost:9000/ AWS_URL=http://localhost:9000/ AWS_USE_PATH_STYLE_ENDPOINT=false -AWS_BUCKET_PUBLIC=nmrxiv-public +# ============================================================================ +# Pusher Configuration +# ============================================================================ PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= @@ -85,42 +117,105 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +# ============================================================================ +# Scout & Meilisearch Configuration +# ============================================================================ SCOUT_DRIVER=meilisearch SCOUT_PREFIX=dev_ MEILISEARCH_HOST=http://localhost:7700/ MEILISEARCH_KEY= MEILISEARCH_PUBLICKEY= +# ============================================================================ +# OAuth Providers +# ============================================================================ +# GitHub OAuth GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GITHUB_REDIRECT_URL=http://localhost:80/auth/login/github/callback +# Twitter OAuth TWITTER_CLIENT_ID= TWITTER_CLIENT_SECRET= TWITTER_REDIRECT_URL=http://localhost:80/auth/login/twitter/callback +# ORCID OAuth ORCID_CLIENT_ID= ORCID_CLIENT_SECRET= ORCID_REDIRECT_URL=http://localhost/auth/login/orcid/callback ORCID_ENVIRONMENT=sandbox +ORCID_UID_FIELDNAME= +ORCID_BASE_URL=https://pub.orcid.org/v3.0 +# NFDI4Chem AAI OAuth NFDIAAI_CLIENT_ID= NFDIAAI_CLIENT_SECRET= NFDIAAI_REDIRECT_URL="${APP_URL}/auth/login/regapp/callback" -TELESCOPE_ENABLED=false - -#DATACITE Properties +# ============================================================================ +# DataCite & DOI Configuration +# ============================================================================ DOI_HOST=datacite DATACITE_USERNAME= DATACITE_SECRET= DATACITE_PREFIX= -DATACITE_ENDPOINT= +DATACITE_ENDPOINT=https://api.test.datacite.org +DATACITE_API=https://api.datacite.org/ -NMRKIT_URL=https://nodejs.nmrxiv.org -CAS_URL=https://commonchemistry.cas.org +# ============================================================================ +# External API Services +# ============================================================================ +# ROR (Research Organization Registry) +ROR_API_URL=https://api.ror.org/organizations +ROR_CLIENT_ID= + +# Europe PMC & CrossRef +EUROPEMC_WS_API=https://www.ebi.ac.uk/europepmc/webservices/rest/search +CROSSREF_API=https://api.crossref.org/works/ + +# ChemInformatics API +CM_API=https://api.cheminf.studio/latest/ + +# CAS (Chemical Abstracts Service) +CAS_API_TOKEN= + +# PubChem PUBCHEM_URL=https://pubchem.ncbi.nlm.nih.gov -COMMON_CHEMISTRY_URL=https://commonchemistry.cas.org + +# Common Chemistry +COMMON_CHEMISTRY_URL=https://commonchemistry.cas.org/api + +# Chemistry Standardization CHEMISTRY_STANDARDIZE_URL=https://api.cheminf.studio/latest/chem/standardize -BACKUP_KEEP_DAYS=7 \ No newline at end of file +# ============================================================================ +# NMRIUM Specific Services +# ============================================================================ +NMRKIT_URL=https://nodejs.nmrxiv.org +NMRIUM_URL=https://nmrium.nmrxiv.org?defaultEmptyMessage=loading&workspace=nmrXiv + +# ============================================================================ +# External Links +# ============================================================================ +MICHI_STANDARDS_URL=https://nfdi4chem.github.io/workshops/docs/michi/tabular/nmr/v1/table + +# ============================================================================ +# Content Security Policy (CSP) Configuration +# ============================================================================ +CSP_ENABLED=true +CSP_NONCE_ENABLED=false +CSP_ENABLED_WHILE_HOT_RELOADING=false + +# Additional CSP sources (comma-separated, no spaces) +# CSP_ADDITIONAL_CONNECT_SRC="https://api.example.com,https://analytics.example.com" +# CSP_ADDITIONAL_IMG_SRC="https://cdn.example.com,https://images.example.com" +# CSP_ADDITIONAL_SCRIPT_SRC="https://cdn.example.com" +# CSP_ADDITIONAL_STYLE_SRC="https://fonts.example.com" + +# ============================================================================ +# Laravel Octane Configuration +# ============================================================================ +OCTANE_SERVER=frankenphp +OCTANE_MAX_EXECUTION_TIME=3600 +OCTANE_HTTPS=true +SUPERVISOR_PHP_COMMAND="/usr/local/bin/php -d variables_order=EGPCS -d max_execution_time=600 -d max_input_time=600 -d memory_limit=4G /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=80" diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index eb396fac..2b4b1bb3 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -16,8 +16,9 @@ env: REPOSITORY_NAMESPACE: nfdi4chem jobs: + # Lint and Security check lint-security: - name: Lint & Security (reusable) + name: Lint & Security uses: ./.github/workflows/lint-security-check.yml permissions: contents: read @@ -27,11 +28,33 @@ jobs: run_js: true run_secrets: true + # Run tests and collect coverage + test-coverage: + name: Tests & Coverage + uses: ./.github/workflows/test-coverage.yml + needs: [lint-security] + secrets: inherit + + # Smoke test Docker images before pushing + smoke-test: + name: Smoke test Docker images + uses: ./.github/workflows/docker-smoke-test.yml + needs: [test-coverage] + # Build and publish Docker images for the development environment - setup-build-publish-deploy: - name: Build & deploy to development + build-and-push: + name: Build & push to Docker Hub runs-on: ubuntu-latest - needs: [lint-security] + needs: [test-coverage, smoke-test, lint-security] + strategy: + matrix: + image: + - name: app + file: ./deployment/Dockerfile + tag: app-dev-latest + - name: worker + file: ./deployment/Dockerfile.worker + tag: worker-dev-latest # Environment provides secrets and protection rules environment: @@ -52,28 +75,15 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # Build + push app image (tag: app-dev-latest). Uses GHA cache for speed. - - name: Build and push App Docker image - uses: docker/build-push-action@v6 - with: - context: . - file: ./deployment/Dockerfile - push: true - build-args: | - RELEASE_VERSION=app-dev-latest - tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-dev-latest - cache-from: type=gha - cache-to: type=gha,mode=max - - # Build + push worker image (tag: worker-dev-latest) - - name: Build and push Worker Docker image + # Build and push image + - name: Build and push ${{ matrix.image.name }} image uses: docker/build-push-action@v6 with: context: . - file: ./deployment/Dockerfile.worker + file: ${{ matrix.image.file }} push: true build-args: | - RELEASE_VERSION=worker-dev-latest - tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-dev-latest - cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + RELEASE_VERSION=${{ matrix.image.tag }} + tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.image.tag }} + cache-from: type=gha,scope=${{ matrix.image.name }} + cache-to: type=gha,mode=max,scope=${{ matrix.image.name }} \ No newline at end of file diff --git a/.github/workflows/docker-smoke-test.yml b/.github/workflows/docker-smoke-test.yml new file mode 100644 index 00000000..faeb8a8f --- /dev/null +++ b/.github/workflows/docker-smoke-test.yml @@ -0,0 +1,62 @@ +name: Docker Image Smoke Tests + +on: + workflow_call: + +env: + REPOSITORY_NAME: nmrxiv + REPOSITORY_NAMESPACE: nfdi4chem + +jobs: + smoke-test: + name: Smoke test containers + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + container: + - name: app + file: ./deployment/Dockerfile + wait-time: 60 + - name: worker + file: ./deployment/Dockerfile.worker + wait-time: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and export ${{ matrix.container.name }} image + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.container.file }} + load: true + tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.container.name }}-test + cache-from: type=gha,scope=${{ matrix.container.name }} + cache-to: type=gha,mode=max,scope=${{ matrix.container.name }} + + - name: Start ${{ matrix.container.name }} container + run: | + docker run -d --name nmrxiv-${{ matrix.container.name }}-test \ + -e APP_KEY=base64:$(openssl rand -base64 32) \ + -e APP_ENV=production \ + -e DB_CONNECTION=sqlite \ + -e CACHE_STORE=array \ + -e SESSION_DRIVER=array \ + -e QUEUE_CONNECTION=sync \ + ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.container.name }}-test + + - name: Wait for ${{ matrix.container.name }} container health + uses: stringbean/docker-healthcheck-action@v3 + with: + container: nmrxiv-${{ matrix.container.name }}-test + wait-time: ${{ matrix.container.wait-time }} + require-status: running + require-healthy: true + + - name: Cleanup ${{ matrix.container.name }} container + if: always() + run: docker rm -f nmrxiv-${{ matrix.container.name }}-test diff --git a/.github/workflows/lint-security-check.yml b/.github/workflows/lint-security-check.yml index 287ab2d4..166f8cc5 100644 --- a/.github/workflows/lint-security-check.yml +++ b/.github/workflows/lint-security-check.yml @@ -40,7 +40,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' coverage: none tools: composer extensions: mbstring, intl, pdo, pdo_mysql, pdo_pgsql diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint-test.yml similarity index 55% rename from .github/workflows/pr-lint.yml rename to .github/workflows/pr-lint-test.yml index fb1d6f38..bb1454ff 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint-test.yml @@ -1,4 +1,4 @@ -name: PR Lint & Security +name: PR Lint, Security and Tests on: pull_request: @@ -10,12 +10,21 @@ on: permissions: contents: read security-events: write + pull-requests: write + checks: write jobs: lint-security: name: Lint & Security (PR) + if: github.event.pull_request.merged != true uses: ./.github/workflows/lint-security-check.yml with: run_php: true run_js: true run_secrets: true + + test-coverage: + name: Tests & Coverage (PHP 8.4) + if: github.event.pull_request.merged != true + uses: ./.github/workflows/test-coverage.yml + secrets: inherit diff --git a/.github/workflows/prod-build.yml b/.github/workflows/prod-build.yml index 0cd34103..36ab361d 100644 --- a/.github/workflows/prod-build.yml +++ b/.github/workflows/prod-build.yml @@ -20,22 +20,10 @@ env: REPOSITORY_NAMESPACE: nfdi4chem jobs: - lint-security: - name: Lint & Security (reusable) - uses: ./.github/workflows/lint-security-check.yml - permissions: - contents: read - security-events: write - with: - run_php: true - run_js: true - run_secrets: true - # Guard: confirm input and authorize actor guard: name: Access control and confirmation runs-on: ubuntu-latest - needs: [lint-security] steps: - name: Validate actor and confirmation shell: bash @@ -62,11 +50,43 @@ jobs: fi echo "Authorization check passed." + lint-security: + name: Lint & Security + uses: ./.github/workflows/lint-security-check.yml + needs: [guard] + permissions: + contents: read + security-events: write + with: + run_php: true + run_js: true + run_secrets: true + + # Run tests and collect coverage + test-coverage: + name: Tests & Coverage + uses: ./.github/workflows/test-coverage.yml + needs: [guard, lint-security] + secrets: inherit + + # Smoke test Docker images before pushing + smoke-test: + name: Smoke test Docker images + uses: ./.github/workflows/docker-smoke-test.yml + needs: [guard, test-coverage] + # Build and publish Docker images for the production environment - setup-build-publish-deploy-prod: - name: Deploy to prod + build-and-push: + name: Build & push to Docker Hub runs-on: ubuntu-latest - needs: [guard] + needs: [guard, test-coverage, smoke-test] + strategy: + matrix: + image: + - name: app + file: ./deployment/Dockerfile + - name: worker + file: ./deployment/Dockerfile.worker environment: name: Prod steps: @@ -102,34 +122,18 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # Build + push app image (tags: release, sha, latest). Uses GHA cache. - - name: Build and push App Docker image - uses: docker/build-push-action@v6 - with: - context: . - file: ./deployment/Dockerfile - push: true - build-args: | - RELEASE_VERSION=${{ steps.fetch-latest-release.outputs.tag_name }} - tags: | - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-sha-${{ steps.fetch-latest-release.outputs.sha_short }} - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-${{ steps.fetch-latest-release.outputs.tag_name }} - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-latest - cache-from: type=gha - cache-to: type=gha,mode=max - - # Build + push worker image (tags: release, sha, latest) - - name: Build and push Worker Docker image + # Build and push image + - name: Build and push ${{ matrix.image.name }} image uses: docker/build-push-action@v6 with: context: . - file: ./deployment/Dockerfile.worker + file: ${{ matrix.image.file }} push: true build-args: | RELEASE_VERSION=${{ steps.fetch-latest-release.outputs.tag_name }} tags: | - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-sha-${{ steps.fetch-latest-release.outputs.sha_short }} - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-${{ steps.fetch-latest-release.outputs.tag_name }} - ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-latest - cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.image.name }}-sha-${{ steps.fetch-latest-release.outputs.sha_short }} + ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.image.name }}-${{ steps.fetch-latest-release.outputs.tag_name }} + ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:${{ matrix.image.name }}-latest + cache-from: type=gha,scope=${{ matrix.image.name }} + cache-to: type=gha,mode=max,scope=${{ matrix.image.name }} \ No newline at end of file diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml new file mode 100644 index 00000000..f06a487b --- /dev/null +++ b/.github/workflows/test-coverage.yml @@ -0,0 +1,79 @@ +name: PHPUnit Tests & Coverage Analysis + +on: + workflow_call: + +jobs: + test-coverage: + name: Tests & Coverage (PHP 8.4) + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:17 + env: + POSTGRES_DB: nmrxiv_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, soap, intl, gd, exif, iconv + coverage: pcov + + - name: Cache Composer packages + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install composer dependencies + run: composer install --ignore-platform-reqs + + - name: Prepare Laravel Application + run: | + php -r "file_exists('.env') || copy('.env.ci', '.env');" + + echo AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID_DEV }} >> .env + echo AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} >> .env + echo MEILISEARCH_KEY=${{ secrets.MEILISEARCH_KEY_DEV }} >> .env + echo MEILISEARCH_PUBLICKEY=${{ secrets.MEILISEARCH_PUBLICKEY_DEV }} >> .env + + php artisan key:generate + php artisan migrate --seed + + - name: Cache Node modules + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install front-end dependencies + run: npm ci + + - name: Build front-end assets + run: npm run build + + - name: Run tests and collect coverage + run: vendor/bin/phpunit --coverage-clover coverage.xml --display-skipped + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 3978544a..b262e1c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,14 @@ /public/hot /public/build /public/storage +/public/vendor +/public/vendor/swagger-api /storage/*.key /storage/pail +/storage/app/spectra_parse /vendor .env .env.production -.env.* .env.backup .phpunit.result.cache docker-compose.override.yml @@ -24,3 +26,8 @@ docs/.vitepress/cache **/caddy frankenphp frankenphp-worker.php + +# Coverage reports +coverage.xml +coverage-report/ +.phpunit.cache diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..2312dc58 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index 4a6a82fc..00000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Tests\\Feature\\ApiTokenPermissionsTest::test_api_token_permissions_can_be_updated":8,"Tests\\Feature\\AuthenticationTest::test_login_screen_can_be_rendered":8,"Tests\\Feature\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":8,"Tests\\Feature\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":8,"Tests\\Feature\\BrowserSessionsTest::test_other_browser_sessions_can_be_logged_out":8,"Tests\\Feature\\CreateApiTokenTest::test_api_tokens_can_be_created":8,"Tests\\Feature\\CreateTeamTest::test_teams_can_be_created":8,"Tests\\Feature\\DeleteAccountTest::test_user_accounts_can_be_deleted":1,"Tests\\Feature\\DeleteAccountTest::test_correct_password_must_be_provided_before_account_can_be_deleted":1,"Tests\\Feature\\DeleteApiTokenTest::test_api_tokens_can_be_deleted":8,"Tests\\Feature\\DeleteTeamTest::test_teams_can_be_deleted":8,"Tests\\Feature\\DeleteTeamTest::test_personal_teams_cant_be_deleted":8,"Tests\\Feature\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":8,"Tests\\Feature\\EmailVerificationTest::test_email_can_be_verified":8,"Tests\\Feature\\EmailVerificationTest::test_email_can_not_verified_with_invalid_hash":8,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\InviteTeamMemberTest::test_team_members_can_be_invited_to_team":8,"Tests\\Feature\\InviteTeamMemberTest::test_team_member_invitations_can_be_cancelled":8,"Tests\\Feature\\LeaveTeamTest::test_users_can_leave_teams":8,"Tests\\Feature\\LeaveTeamTest::test_team_owners_cant_leave_their_own_team":8,"Tests\\Feature\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":8,"Tests\\Feature\\PasswordConfirmationTest::test_password_can_be_confirmed":8,"Tests\\Feature\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_can_be_requested":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_screen_can_be_rendered":8,"Tests\\Feature\\PasswordResetTest::test_password_can_be_reset_with_valid_token":8,"Tests\\Feature\\ProfileInformationTest::test_profile_information_can_be_updated":8,"Tests\\Feature\\RegistrationTest::test_registration_screen_can_be_rendered":8,"Tests\\Feature\\RegistrationTest::test_registration_screen_cannot_be_rendered_if_support_is_disabled":1,"Tests\\Feature\\RegistrationTest::test_new_users_can_register":8,"Tests\\Feature\\RemoveTeamMemberTest::test_team_members_can_be_removed_from_teams":8,"Tests\\Feature\\RemoveTeamMemberTest::test_only_team_owner_can_remove_team_members":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_enabled":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_recovery_codes_can_be_regenerated":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_disabled":8,"Tests\\Feature\\UpdatePasswordTest::test_password_can_be_updated":8,"Tests\\Feature\\UpdatePasswordTest::test_current_password_must_be_correct":8,"Tests\\Feature\\UpdatePasswordTest::test_new_passwords_must_match":8,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_team_member_roles_can_be_updated":8,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_only_team_owner_can_update_team_member_roles":8,"Tests\\Feature\\UpdateTeamNameTest::test_team_names_can_be_updated":8,"Tests\\Feature\\OEmbedTest::it_can_generate_oembed_response_for_study":8,"Tests\\Feature\\OEmbedTest::it_can_generate_oembed_response_for_dataset":8,"Tests\\Feature\\OEmbedTest::it_accepts_custom_width_and_height_parameters":8,"Tests\\Feature\\OEmbedTest::it_handles_study_without_thumbnail":8,"Tests\\Feature\\OEmbedTest::it_returns_400_when_url_parameter_is_missing":8,"Tests\\Feature\\OEmbedTest::it_returns_400_when_url_format_is_invalid":8,"Tests\\Feature\\OEmbedTest::it_returns_400_when_identifier_is_missing_from_url":8,"Tests\\Feature\\OEmbedTest::it_returns_404_when_identifier_cannot_be_resolved":8,"Tests\\Feature\\OEmbedTest::it_returns_404_when_identifier_format_is_invalid":8,"Tests\\Feature\\OEmbedTest::it_can_render_embedded_study_content":8,"Tests\\Feature\\OEmbedTest::it_can_render_embedded_dataset_content":8,"Tests\\Feature\\OEmbedTest::it_returns_400_when_embed_identifier_is_empty":8,"Tests\\Feature\\OEmbedTest::it_returns_404_when_embed_identifier_cannot_be_resolved":8,"Tests\\Feature\\OEmbedTest::it_returns_404_when_embed_identifier_format_is_invalid":8,"Tests\\Feature\\OEmbedTest::it_returns_400_for_unsupported_content_type_in_embed":8,"Tests\\Feature\\OEmbedTest::it_handles_dataset_without_associated_study":8,"Tests\\Feature\\OEmbedTest::it_handles_nmrxiv_prefix_in_identifier":8,"Tests\\Feature\\OEmbedTest::it_handles_case_insensitive_identifiers":8,"Tests\\Feature\\OEmbedTest::it_supports_json_format_parameter":8,"Tests\\Feature\\OEmbedTest::it_handles_server_errors_gracefully":8,"Tests\\Feature\\OEmbedTest::test_it_can_generate_oembed_response_for_study":7,"Tests\\Feature\\OEmbedTest::test_it_can_generate_oembed_response_for_dataset":8,"Tests\\Feature\\OEmbedTest::test_it_accepts_custom_width_and_height_parameters":8,"Tests\\Feature\\OEmbedTest::test_it_handles_study_without_thumbnail":8,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_url_parameter_is_missing":7,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_url_format_is_invalid":7,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_identifier_is_missing_from_url":8,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_identifier_cannot_be_resolved":7,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_identifier_format_is_invalid":7,"Tests\\Feature\\OEmbedTest::test_it_can_render_embedded_study_content":7,"Tests\\Feature\\OEmbedTest::test_it_can_render_embedded_dataset_content":7,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_embed_identifier_is_empty":8,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_embed_identifier_cannot_be_resolved":7,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_embed_identifier_format_is_invalid":7,"Tests\\Feature\\OEmbedTest::test_it_returns_400_for_unsupported_content_type_in_embed":7,"Tests\\Feature\\OEmbedTest::test_it_handles_dataset_without_associated_study":7,"Tests\\Feature\\OEmbedTest::test_it_handles_nmrxiv_prefix_in_identifier":8,"Tests\\Feature\\OEmbedTest::test_it_handles_case_insensitive_identifiers":8,"Tests\\Feature\\OEmbedTest::test_it_supports_json_format_parameter":8,"Tests\\Feature\\OEmbedTest::test_it_handles_server_errors_gracefully":8,"Tests\\Feature\\OEmbedTest::test_it_validates_width_and_height_parameters":7,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_with_json_data":7,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_creates_new_authors":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_reuses_existing_authors_by_email":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_handles_empty_array":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_validates_required_fields":8,"Tests\\Unit\\AuthorServiceTest::test_remove_author_from_project_detaches_successfully":8,"Tests\\Unit\\AuthorServiceTest::test_remove_author_from_project_handles_nonexistent_author":8,"Tests\\Unit\\AuthorServiceTest::test_update_contributor_type_changes_role":8,"Tests\\Unit\\AuthorServiceTest::test_update_contributor_type_validates_role":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_uses_database_transaction":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_creates_composite_index_optimized_queries":8,"Tests\\Unit\\AuthorServiceTest::test_sync_authors_prevents_n_plus_one_queries":8,"Tests\\Unit\\ProcessDraftELNSubmissionProxyTest::test_http_client_uses_proxy_when_configured":8,"Tests\\Unit\\ProcessDraftELNSubmissionProxyTest::test_http_client_works_without_proxy_configuration":8,"Tests\\Feature\\ManageAuthorsTest::test_author_cannot_be_updated_or_detached_if_project_is_public":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_can_be_created_with_submitted_through_field":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_creates_sample_with_null_submitted_through_by_default":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_can_create_sample_with_eln_state":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_can_create_sample_with_custom_submitted_through":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_can_be_updated_with_submitted_through_field":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_is_fillable":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_accepts_null_values":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_accepts_various_string_values":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_store_processing_logs_from_draft":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_be_created_with_submitted_through_field":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_creates_study_with_null_submitted_through_by_default":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_eln_state":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_default_eln":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_custom_submitted_through":8,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_be_updated_with_submitted_through_field":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_tracking_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_tracking_authentication_failure":5,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_trackings_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_by_id_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_items_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_item_by_name_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_eln_submission_tracking_success":7,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_update_eln_submission_status_success":8,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_api_error_handling":5,"Tests\\Feature\\ELNSubmissionTrackingTest::test_eln_submission_creates_tracking_when_received":7,"Tests\\Feature\\ELNSubmissionTrackingTest::test_study_publication_creates_tracking_when_published":8,"Tests\\Feature\\ELNSubmissionTrackingTest::test_tracking_failure_does_not_break_submission":7,"Tests\\Feature\\ELNSubmissionTrackingTest::test_non_eln_study_publication_does_not_create_tracking":8,"Tests\\Feature\\SearchControllerSecurityTest::test_sql_injection_in_query_parameter":8,"Tests\\Feature\\SearchControllerSecurityTest::test_sql_injection_in_filter_queries":7,"Tests\\Feature\\SearchControllerSecurityTest::test_input_validation":8,"Tests\\Feature\\SearchControllerSecurityTest::test_query_length_limits":8,"Tests\\Feature\\SearchControllerSecurityTest::test_control_character_filtering":8,"Tests\\Feature\\SearchControllerSecurityTest::test_smiles_injection_attempts":7,"Tests\\Feature\\SearchControllerSecurityTest::test_inchi_injection_attempts":8,"Tests\\Feature\\SearchControllerSecurityTest::test_legitimate_queries_still_work":8,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_handles_ranges":5,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_sanitizes_text":5,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_validates_booleans":5,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_validates_database_names":5,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_malicious_filter_queries_handled_safely":5,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_allows_safe_html":7,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_preserves_markdown_formatting":7,"Tests\\Unit\\MarkdownXSSSecurityTest::test_javascript_urls_are_removed":7,"Tests\\Feature\\RouteStructureTest::test_project_route_accepts_valid_identifiers":8,"Tests\\Feature\\RouteStructureTest::test_sample_route_accepts_valid_identifiers":8,"Tests\\Feature\\RouteStructureTest::test_compound_route_accepts_valid_identifiers":8},"times":{"Tests\\Unit\\ExampleTest::test_example":0.003,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.181,"Tests\\Feature\\ApiTokenPermissionsTest::test_api_token_permissions_can_be_updated":0.047,"Tests\\Feature\\AuthenticationTest::test_login_screen_can_be_rendered":0.035,"Tests\\Feature\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":0.061,"Tests\\Feature\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.052,"Tests\\Feature\\BrowserSessionsTest::test_other_browser_sessions_can_be_logged_out":0.099,"Tests\\Feature\\CreateApiTokenTest::test_api_tokens_can_be_created":0.01,"Tests\\Feature\\CreateTeamTest::test_teams_can_be_created":0.011,"Tests\\Feature\\DeleteAccountTest::test_user_accounts_can_be_deleted":0.013,"Tests\\Feature\\DeleteAccountTest::test_correct_password_must_be_provided_before_account_can_be_deleted":0,"Tests\\Feature\\DeleteApiTokenTest::test_api_tokens_can_be_deleted":0.007,"Tests\\Feature\\DeleteTeamTest::test_teams_can_be_deleted":0.071,"Tests\\Feature\\DeleteTeamTest::test_personal_teams_cant_be_deleted":0.211,"Tests\\Feature\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":0.015,"Tests\\Feature\\EmailVerificationTest::test_email_can_be_verified":0.007,"Tests\\Feature\\EmailVerificationTest::test_email_can_not_verified_with_invalid_hash":0.007,"Tests\\Feature\\InviteTeamMemberTest::test_team_members_can_be_invited_to_team":0.019,"Tests\\Feature\\InviteTeamMemberTest::test_team_member_invitations_can_be_cancelled":0.007,"Tests\\Feature\\LeaveTeamTest::test_users_can_leave_teams":0.01,"Tests\\Feature\\LeaveTeamTest::test_team_owners_cant_leave_their_own_team":0.006,"Tests\\Feature\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":0.012,"Tests\\Feature\\PasswordConfirmationTest::test_password_can_be_confirmed":0.047,"Tests\\Feature\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.209,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":0.007,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_can_be_requested":0.216,"Tests\\Feature\\PasswordResetTest::test_reset_password_screen_can_be_rendered":0.216,"Tests\\Feature\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.229,"Tests\\Feature\\ProfileInformationTest::test_profile_information_can_be_updated":0.078,"Tests\\Feature\\RegistrationTest::test_registration_screen_can_be_rendered":0.006,"Tests\\Feature\\RegistrationTest::test_registration_screen_cannot_be_rendered_if_support_is_disabled":0,"Tests\\Feature\\RegistrationTest::test_new_users_can_register":0.012,"Tests\\Feature\\RemoveTeamMemberTest::test_team_members_can_be_removed_from_teams":0.008,"Tests\\Feature\\RemoveTeamMemberTest::test_only_team_owner_can_remove_team_members":0.009,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_enabled":0.009,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_recovery_codes_can_be_regenerated":0.009,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_disabled":0.006,"Tests\\Feature\\UpdatePasswordTest::test_password_can_be_updated":0.048,"Tests\\Feature\\UpdatePasswordTest::test_current_password_must_be_correct":0.086,"Tests\\Feature\\UpdatePasswordTest::test_new_passwords_must_match":0.086,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_team_member_roles_can_be_updated":0.01,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_only_team_owner_can_update_team_member_roles":0.746,"Tests\\Feature\\UpdateTeamNameTest::test_team_names_can_be_updated":0.008,"Tests\\Feature\\OEmbedTest::test_it_can_generate_oembed_response_for_study":0.009,"Tests\\Feature\\OEmbedTest::test_it_can_generate_oembed_response_for_dataset":0.005,"Tests\\Feature\\OEmbedTest::test_it_accepts_custom_width_and_height_parameters":0.005,"Tests\\Feature\\OEmbedTest::test_it_handles_study_without_thumbnail":0.007,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_url_parameter_is_missing":0.005,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_url_format_is_invalid":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_identifier_is_missing_from_url":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_identifier_cannot_be_resolved":0.007,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_identifier_format_is_invalid":0.009,"Tests\\Feature\\OEmbedTest::test_it_can_render_embedded_study_content":0.017,"Tests\\Feature\\OEmbedTest::test_it_can_render_embedded_dataset_content":0.013,"Tests\\Feature\\OEmbedTest::test_it_returns_400_when_embed_identifier_is_empty":0.005,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_embed_identifier_cannot_be_resolved":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_404_when_embed_identifier_format_is_invalid":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_400_for_unsupported_content_type_in_embed":0.012,"Tests\\Feature\\OEmbedTest::test_it_handles_dataset_without_associated_study":0.006,"Tests\\Feature\\OEmbedTest::test_it_handles_nmrxiv_prefix_in_identifier":0.006,"Tests\\Feature\\OEmbedTest::test_it_handles_case_insensitive_identifiers":0.008,"Tests\\Feature\\OEmbedTest::test_it_supports_json_format_parameter":0.006,"Tests\\Feature\\OEmbedTest::test_it_handles_server_errors_gracefully":0.006,"Tests\\Feature\\OEmbedTest::test_it_returns_500_when_identifier_format_is_invalid":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_500_when_embed_identifier_format_is_invalid":0.004,"Tests\\Feature\\OEmbedTest::test_it_returns_404_for_unsupported_content_type_in_embed":0.005,"Tests\\Feature\\OEmbedTest::test_it_blocks_private_content_in_oembed":0.007,"Tests\\Feature\\OEmbedTest::test_it_blocks_private_content_in_embed":0.006,"Tests\\Feature\\OEmbedTest::test_it_blocks_external_domain_urls":0.007,"Tests\\Feature\\OEmbedTest::test_it_validates_width_and_height_parameters":0.009,"Tests\\Feature\\OEmbedTest::test_iframe_html_is_properly_sanitized":0.005,"Tests\\Feature\\ManageAuthorsTest::test_author_can_be_created_and_updated":0.025,"Tests\\Feature\\ManageAuthorsTest::test_author_can_be_updated":0.024,"Tests\\Feature\\ManageAuthorsTest::test_author_can_be_detached":0.019,"Tests\\Feature\\ManageAuthorsTest::test_author_cannot_be_updated_or_deleted_by_reviewer":0.016,"Tests\\Feature\\ManageAuthorsTest::test_author_cannot_be_updated_or_detached_if_project_is_public":0.013,"Tests\\Feature\\ManageAuthorsTest::test_role_of_an_author_can_be_updated":0.013,"Tests\\Feature\\ManageAuthorsTest::test_role_of_an_author_cannot_be_updated_by_reviewer":0.011,"Tests\\Feature\\ManageAuthorsTest::test_role_of_an_author_cannot_be_updated_for_random_contributor_types":0.011,"Tests\\Unit\\ProcessDraftELNSubmissionProxyTest::test_http_client_uses_proxy_when_configured":0.067,"Tests\\Unit\\ProcessDraftELNSubmissionProxyTest::test_http_client_works_without_proxy_configuration":0.003,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_preserves_newlines":0.003,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_removes_problematic_unicode":0,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_preserves_ascii_printable":0,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_with_json_data":0,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_in_array_preserves_newlines":0,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_in_nmrium_data_preserves_newlines":0,"Tests\\Unit\\HelperFunctionsTest::test_sanitize_unicode_string_converts_escape_sequences":0,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_can_be_created_with_submitted_through_field":0.007,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_creates_sample_with_null_submitted_through_by_default":0.002,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_can_create_sample_with_eln_state":0.002,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_factory_can_create_sample_with_custom_submitted_through":0.002,"Tests\\Feature\\SampleSubmittedThroughTest::test_sample_can_be_updated_with_submitted_through_field":0.002,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_is_fillable":0.001,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_accepts_null_values":0.003,"Tests\\Feature\\SampleSubmittedThroughTest::test_submitted_through_field_accepts_various_string_values":0.01,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_be_created_with_submitted_through_field":0.008,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_creates_study_with_null_submitted_through_by_default":0.004,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_eln_state":0.003,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_default_eln":0.003,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_factory_can_create_study_with_custom_submitted_through":0.003,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_be_updated_with_submitted_through_field":0.005,"Tests\\Feature\\SampleSubmittedThroughTest::test_study_can_store_processing_logs_from_draft":0.003,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_tracking_success":0.02,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_tracking_authentication_failure":0.004,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_trackings_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_by_id_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_items_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_get_tracking_item_by_name_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_create_eln_submission_tracking_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_update_eln_submission_status_success":0.001,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_api_error_handling":0.001,"Tests\\Feature\\ELNSubmissionTrackingTest::test_eln_submission_creates_tracking_when_received":0.356,"Tests\\Feature\\ELNSubmissionTrackingTest::test_study_publication_creates_tracking_when_published":0.025,"Tests\\Feature\\ELNSubmissionTrackingTest::test_tracking_failure_does_not_break_submission":0.015,"Tests\\Feature\\ELNSubmissionTrackingTest::test_non_eln_study_publication_does_not_create_tracking":0.007,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_is_enabled_returns_correct_value":0,"Tests\\Unit\\ChemotionRepositoryTrackerServiceTest::test_status_validation":0.001,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_sanitize_query_removes_control_characters":0.003,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_sanitize_query_trims_whitespace":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_sanitize_query_limits_length":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_sanitize_query_handles_null":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_validates_fields":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_handles_ranges":0.005,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_sanitizes_text":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_validates_booleans":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_build_secure_filter_query_validates_database_names":0,"Tests\\Unit\\SearchControllerSecurityUnitTest::test_malicious_filter_queries_handled_safely":0,"Tests\\Feature\\SearchControllerSecurityTest::test_sql_injection_in_query_parameter":0.037,"Tests\\Feature\\SearchControllerSecurityTest::test_sql_injection_in_filter_queries":0.012,"Tests\\Feature\\SearchControllerSecurityTest::test_input_validation":0.008,"Tests\\Feature\\SearchControllerSecurityTest::test_query_length_limits":0.003,"Tests\\Feature\\SearchControllerSecurityTest::test_control_character_filtering":0.003,"Tests\\Feature\\SearchControllerSecurityTest::test_smiles_injection_attempts":0.006,"Tests\\Feature\\SearchControllerSecurityTest::test_inchi_injection_attempts":0.005,"Tests\\Feature\\SearchControllerSecurityTest::test_legitimate_queries_still_work":0.006,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_sanitizes_script_tags":0.004,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_allows_safe_html":0.008,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_handles_empty_input":0,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_prevents_various_xss_attacks":0,"Tests\\Unit\\MarkdownSecurityTest::test_md_function_preserves_markdown_formatting":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_script_tags_are_removed":0.003,"Tests\\Unit\\MarkdownXSSSecurityTest::test_event_handlers_are_removed":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_javascript_urls_are_removed":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_dangerous_html_tags_are_removed":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_safe_markdown_is_preserved":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_empty_input_is_handled_safely":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_sanitize_html_function_prevents_xss":0,"Tests\\Unit\\MarkdownXSSSecurityTest::test_sanitize_html_handles_license_content":0,"Tests\\Feature\\RouteStructureTest::test_new_project_route_structure_is_registered":0.003,"Tests\\Feature\\RouteStructureTest::test_new_sample_route_structure_is_registered":0,"Tests\\Feature\\RouteStructureTest::test_new_compound_route_structure_is_registered":0,"Tests\\Feature\\RouteStructureTest::test_project_route_accepts_valid_identifiers":0.001,"Tests\\Feature\\RouteStructureTest::test_sample_route_accepts_valid_identifiers":0,"Tests\\Feature\\RouteStructureTest::test_compound_route_accepts_valid_identifiers":0}} \ No newline at end of file diff --git a/README.md b/README.md index 56486cb6..4d9eb704 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://GitHub.com/NFDI4Chem/nmrxiv/graphs/commit-activity) [![GitHub issues](https://img.shields.io/github/issues/NFDI4Chem/nmrxiv.svg)](https://GitHub.com/NFDI4Chem/nmrxiv/issues/) [![GitHub contributors](https://img.shields.io/github/contributors/NFDI4Chem/nmrxiv.svg)](https://GitHub.com/NFDI4Chem/nmrxiv/graphs/contributors/) -![Workflow](https://github.com/NFDI4Chem/nmrxiv/actions/workflows/build.yml/badge.svg) +![Workflow](https://github.com/NFDI4Chem/nmrxiv/actions/workflows/prod-build.yml/badge.svg) ![Workflow](https://github.com/NFDI4Chem/nmrxiv/actions/workflows/release-please.yml/badge.svg) [![Powered by Laravel](https://img.shields.io/badge/Powered%20by-Laravel-red.svg?style=flat&logo=Laravel)](https://laravel.com) diff --git a/app/Actions/Author/SyncProjectAuthors.php b/app/Actions/Author/SyncProjectAuthors.php index c24bb053..3ef3094e 100644 --- a/app/Actions/Author/SyncProjectAuthors.php +++ b/app/Actions/Author/SyncProjectAuthors.php @@ -64,6 +64,7 @@ private function validateAuthorData(array $authorData): void 'email_id' => ['nullable', 'email', 'max:320'], 'orcid_id' => ['nullable', 'string', 'max:19'], 'affiliation' => ['nullable', 'string', 'max:500'], + 'ror_id' => ['nullable', 'string', 'max:255'], 'contributor_type' => ['nullable', 'string', 'max:50'], ])->validate(); } @@ -105,6 +106,7 @@ private function prepareAuthorAttributes(array $authorData, string $familyName, 'orcid_id' => $authorData['orcid_id'] ?? null, 'email_id' => $authorData['email_id'] ?? null, 'affiliation' => $authorData['affiliation'] ?? null, + 'ror_id' => $authorData['ror_id'] ?? null, ]; } } diff --git a/app/Actions/Draft/CreateDraft.php b/app/Actions/Draft/CreateDraft.php index c026b0aa..69ed1629 100644 --- a/app/Actions/Draft/CreateDraft.php +++ b/app/Actions/Draft/CreateDraft.php @@ -16,7 +16,7 @@ public function execute(User $user, array $options = []): Draft [$user_id, $team_id] = $user->getUserTeamData(); $id = Str::uuid(); - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = $this->generateDraftPath($environment, $user_id, $id); $name = $this->generateDraftName($id, $options); diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 39ed30de..de985980 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -39,6 +39,7 @@ public function create(array $input) 'username' => $input['username'], 'orcid_id' => $input['orcid_id'], 'affiliation' => $input['affiliation'], + 'ror_id' => $input['ror_id'] ?? null, 'password' => Hash::make($input['password']), ]), function (User $user) { diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index a057a9ec..c3fa7a35 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -25,7 +25,8 @@ public function update($user, array $input) 'username' => ['required', 'string', 'max:255', Rule::unique('users')->ignore($user->id)], 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 'orcid_id' => ['nullable', 'string', 'max:255'], - 'affiliation' => ['nullable', 'string', 'max:255'], + 'affiliation' => ['nullable', 'string'], + 'ror_id' => ['nullable', 'string', 'max:255'], ])->validateWithBag('updateProfileInformation'); if (isset($input['photo'])) { @@ -44,6 +45,7 @@ public function update($user, array $input) 'email' => $input['email'], 'orcid_id' => $input['orcid_id'], 'affiliation' => $input['affiliation'] ? $input['affiliation'] : null, + 'ror_id' => $input['ror_id'] ?? null, ])->save(); } } @@ -64,6 +66,7 @@ protected function updateVerifiedUser($user, array $input) 'email' => $input['email'], 'orcid_id' => $input['orcid_id'], 'affiliation' => $input['affiliation'], + 'ror_id' => $input['ror_id'] ?? null, ])->save(); $user->sendEmailVerificationNotification(); diff --git a/app/Actions/Jetstream/AddTeamMember.php b/app/Actions/Jetstream/AddTeamMember.php index 2e3b8a6f..9221bbf6 100644 --- a/app/Actions/Jetstream/AddTeamMember.php +++ b/app/Actions/Jetstream/AddTeamMember.php @@ -48,7 +48,7 @@ protected function validate($team, string $email, ?string $role) 'email' => $email, 'role' => $role, ], $this->rules(), [ - 'email.exists' => __('We were unable to find a registered user with this email address.'), + 'email.exists' => __('Unable to add member with this email address.'), ])->after( $this->ensureUserIsNotAlreadyOnTeam($team, $email) )->validateWithBag('addTeamMember'); @@ -81,7 +81,7 @@ protected function ensureUserIsNotAlreadyOnTeam($team, string $email) $validator->errors()->addIf( $team->hasUserWithEmail($email), 'email', - __('This user already belongs to the team.') + __('Unable to add member with this email address.') ); }; } diff --git a/app/Actions/Project/ArchiveProject.php b/app/Actions/Project/ArchiveProject.php index c08b6f5d..d0a4d117 100644 --- a/app/Actions/Project/ArchiveProject.php +++ b/app/Actions/Project/ArchiveProject.php @@ -12,14 +12,24 @@ class ArchiveProject * @param mixed $project * @return void */ - public function toggle($project) + public function toggleArchive($project) { $archiveState = ! $project->is_archived; - $project->studies()->update(['is_archived' => $archiveState]); + $project->studies()->update([ + 'is_archived' => $archiveState, + 'status' => $archiveState ? 'archived' : 'published', + ]); + foreach ($project->studies as $study) { - $study->datasets()->update(['is_archived' => $archiveState]); + $study->datasets()->update([ + 'is_archived' => $archiveState, + 'status' => $archiveState ? 'archived' : 'published', + ]); } + $project->is_archived = $archiveState; + $project->status = $archiveState ? 'archived' : 'published'; + if ($project->is_archived) { $project->sendNotification('archival', $this->prepareSendList($project)); } diff --git a/app/Actions/Project/CreateNewProject.php b/app/Actions/Project/CreateNewProject.php index 8de33cca..5cb395ef 100644 --- a/app/Actions/Project/CreateNewProject.php +++ b/app/Actions/Project/CreateNewProject.php @@ -19,7 +19,7 @@ class CreateNewProject */ public function create(array $input) { - $license = $input['license']; + $license = $input['license'] ?? null; $errorMessages = [ 'license.required_if' => 'The license field is required when the project is made public.', ]; diff --git a/app/Actions/Project/DeleteProject.php b/app/Actions/Project/DeleteProject.php index ba027273..5256fb91 100644 --- a/app/Actions/Project/DeleteProject.php +++ b/app/Actions/Project/DeleteProject.php @@ -30,8 +30,8 @@ public function delete($project) } else { $project->studies()->update(['is_deleted' => true]); foreach ($project->studies as $study) { - $study->update(['is_deleted' => true]); - $study->datasets()->update(['is_deleted' => true]); + $study->update(['is_deleted' => true, 'status' => 'deleted']); + $study->datasets()->update(['is_deleted' => true, 'status' => 'deleted']); } $draft = $project->draft; if ($draft) { @@ -40,6 +40,7 @@ public function delete($project) $project->name = $project->name; $project->deleted_on = Carbon::now(); $project->is_deleted = true; + $project->status = 'deleted'; $project->sendNotification('deletion', $this->prepareSendList($project)); } $project->save(); @@ -204,9 +205,9 @@ public function deleteFSO(FileSystemObject $filesystemobject) $fsoIds = $this->getChildrenIds($filesystemobject, []); if (Storage::has($filesystemobject->path)) { if ($filesystemobject->type == 'directory') { - Storage::disk(env('FILESYSTEM_DRIVER'))->deleteDirectory($filesystemobject->path); + Storage::disk(config('filesystems.default'))->deleteDirectory($filesystemobject->path); } else { - Storage::disk(env('FILESYSTEM_DRIVER'))->delete($filesystemobject->path); + Storage::disk(config('filesystems.default'))->delete($filesystemobject->path); } FileSystemObject::whereIn('id', $fsoIds)->delete(); } diff --git a/app/Actions/Project/RestoreProject.php b/app/Actions/Project/RestoreProject.php index e6d0564a..1f119d9c 100644 --- a/app/Actions/Project/RestoreProject.php +++ b/app/Actions/Project/RestoreProject.php @@ -27,8 +27,10 @@ public function restore($project) $study->datasets()->update(['is_deleted' => false]); } $draft = $project->draft; - $draft->is_deleted = false; - $draft->save(); + if ($draft) { + $draft->is_deleted = false; + $draft->save(); + } $project->is_deleted = false; $project->save(); } diff --git a/app/Actions/Project/UpdateProject.php b/app/Actions/Project/UpdateProject.php index aa943341..55cde367 100644 --- a/app/Actions/Project/UpdateProject.php +++ b/app/Actions/Project/UpdateProject.php @@ -21,11 +21,13 @@ public function update(Project $project, array $input) { $errorMessages = [ 'license.required_if' => 'The license field is required when the project is made public.', + 'photo.mimes' => 'The project image must be a file of type: jpg, jpeg, png, gif, webp.', ]; Validator::make($input, [ 'name' => ['required', 'string', 'max:255', Rule::unique('projects') ->where('owner_id', $project->owner_id)->ignore($project->id), ], 'license' => ['required_if:is_public,"true"'], + 'photo' => ['nullable', 'mimes:jpg,jpeg,png,gif,webp', 'max:2048'], ], $errorMessages)->validate(); return DB::transaction(function () use ($input, $project) { @@ -34,7 +36,7 @@ public function update(Project $project, array $input) if (array_key_exists('photo', $input)) { $image = $input['photo']; if (! is_null($image)) { - $s3 = Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC')); + $s3 = Storage::disk(config('filesystems.default_public')); $file_name = uniqid().'.'.$image->getClientOriginalExtension(); $s3filePath = '/projects/'.$file_name; $s3->put($s3filePath, file_get_contents($image), 'public'); diff --git a/app/Actions/Study/AddStudyMember.php b/app/Actions/Study/AddStudyMember.php index dfedef56..2a5fe13b 100644 --- a/app/Actions/Study/AddStudyMember.php +++ b/app/Actions/Study/AddStudyMember.php @@ -47,7 +47,7 @@ protected function validate($study, string $email, ?string $role) 'email' => $email, 'role' => $role, ], $this->rules(), [ - 'email.exists' => __('We were unable to find a registered user with this email address.'), + 'email.exists' => __('Unable to add member with this email address.'), ])->after( $this->ensureUserIsNotAlreadyOnStudy($study, $email) )->validateWithBag('addStudyMember'); @@ -80,7 +80,7 @@ protected function ensureUserIsNotAlreadyOnStudy($study, string $email) $validator->errors()->addIf( $study->hasUserWithEmail($email), 'email', - __('This user already belongs to the study.') + __('Unable to add member with this email address.') ); }; } diff --git a/app/Actions/Study/UpdateStudy.php b/app/Actions/Study/UpdateStudy.php index 5fc4ff16..d8371b61 100644 --- a/app/Actions/Study/UpdateStudy.php +++ b/app/Actions/Study/UpdateStudy.php @@ -4,6 +4,7 @@ use App\Models\Study; use App\Models\Ticker; +use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; @@ -29,7 +30,7 @@ public function update(Study $study, array $input) $study->forceFill([ 'name' => $input['name'], 'slug' => Str::slug($input['name'], '-'), - 'description' => $input['description'] ? $input['description'] : $study->description, + 'description' => array_key_exists('description', $input) ? $input['description'] : $study->description, 'color' => array_key_exists('color', $input) ? $input['color'] : $study->color, 'starred' => array_key_exists('starred', $input) ? $input['starred'] : $study->starred, 'location' => array_key_exists('location', $input) ? $input['location'] : $study->location, @@ -56,30 +57,32 @@ public function update(Study $study, array $input) $release_date = Carbon::now()->timestamp; $sample = $study->sample; - $sampleIdentifier = $sample->identifier ? $sample->identifier : null; + if ($sample) { + $sampleIdentifier = $sample->identifier ? $sample->identifier : null; - if ($sampleIdentifier == null) { - $sampleTicker = Ticker::whereType('sample')->first(); - $sampleIdentifier = $sampleTicker->index + 1; - $sample->identifier = $sampleIdentifier; - $sample->save(); + if ($sampleIdentifier == null) { + $sampleTicker = Ticker::whereType('sample')->first(); + $sampleIdentifier = $sampleTicker->index + 1; + $sample->identifier = $sampleIdentifier; + $sample->save(); - $sampleTicker->index = $sampleIdentifier; - $sampleTicker->save(); - } + $sampleTicker->index = $sampleIdentifier; + $sampleTicker->save(); + } - $molecules = $sample->molecules; + $molecules = $sample->molecules; - foreach ($molecules as $molecule) { - $moleculeIdentifier = $molecule->identifier ? $molecule->identifier : null; - if ($moleculeIdentifier == null) { - $moleculeTicker = Ticker::whereType('molecule')->first(); - $moleculeIdentifier = $moleculeTicker->index + 1; - $molecule->identifier = $moleculeIdentifier; - $molecule->save(); + foreach ($molecules as $molecule) { + $moleculeIdentifier = $molecule->identifier ? $molecule->identifier : null; + if ($moleculeIdentifier == null) { + $moleculeTicker = Ticker::whereType('molecule')->first(); + $moleculeIdentifier = $moleculeTicker->index + 1; + $molecule->identifier = $moleculeIdentifier; + $molecule->save(); - $moleculeTicker->index = $moleculeIdentifier; - $moleculeTicker->save(); + $moleculeTicker->index = $moleculeIdentifier; + $moleculeTicker->save(); + } } } } diff --git a/app/Console/Commands/ExtractSpectra.php b/app/Console/Commands/ExtractSpectra.php deleted file mode 100644 index caf108f9..00000000 --- a/app/Console/Commands/ExtractSpectra.php +++ /dev/null @@ -1,182 +0,0 @@ -get(); - - foreach ($projects as $project) { - echo "\r\n"; - echo $project->identifier; - echo "\r\n"; - $studies = $project->studies; - foreach ($studies as $study) { - echo $study->identifier; - echo "\r\n"; - try { - if (! $study->has_nmrium) { - DB::transaction(function () use ($study) { - $download_url = $study->download_url; - if ($download_url) { - $nmrium_ = $this->processSpectra($download_url); - $parsedSpectra = $nmrium_['data']; - foreach ($parsedSpectra['spectra'] as $spectra) { - unset($spectra['data']); - unset($spectra['meta']); - unset($spectra['originalData']); - unset($spectra['originalInfo']); - } - - $version = $parsedSpectra['version']; - unset($parsedSpectra['version']); - - $nmriumJSON = [ - 'data' => $parsedSpectra, - 'version' => $version, - ]; - - $nmrium = $study->nmrium; - - if ($nmrium) { - $nmrium->nmrium_info = json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE); - $nmrium->save(); - } else { - $nmrium = NMRium::create([ - 'nmrium_info' => json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE), - ]); - $study->nmrium()->save($nmrium); - $study->has_nmrium = true; - $study->save(); - } - } - }); - } - $study = $study->fresh(); - if ($study->has_nmrium) { - $nmriumInfo = $study->nmrium->nmrium_info; - if (count($nmriumInfo['data']['spectra']) == 0) { - echo '--MISSING SPECTRA INFO (NMRIUM JSON)--'; - echo "\r\n"; - } else { - foreach ($study->datasets as $dataset) { - echo $dataset->identifier; - echo "\r\n"; - // echo $dataset->type; - // echo "\r\n"; - if (! $dataset->has_nmrium) { - $nmriumInfo = $study->nmrium->nmrium_info; - $_nmriumJSON = $nmriumInfo; - $fsObject = $dataset->fsObject; - - $studyFSObject = $study->fsObject; - $datasetFSObject = $dataset->fsObject; - - $draft = $study->draft; - - if ($draft && $draft->eln == 'chemotion') { - $path = '/'.$studyFSObject->name.'/'.$datasetFSObject->parent->name.'/'.$datasetFSObject->name; - } else { - $path = '/'.$studyFSObject->name.'/'.$datasetFSObject->name; - } - - $fType = $studyFSObject->type; - - $pathsMatch = false; - $spectrum = []; - $type = []; - foreach ($nmriumInfo['data']['spectra'] as $spectra) { - unset($_nmriumJSON['data']['spectra']); - $files = $spectra['sourceSelector']['files']; - if ($files) { - foreach ($files as $file) { - if (str_contains($file, $fType == 'file' ? $path : $path.'/')) { - $pathsMatch = true; - } - } - } - if ($pathsMatch) { - array_push($spectrum, $spectra); - $experimentDetailsExists = array_key_exists('experiment', $spectra['info']); - if ($experimentDetailsExists) { - $experiment = $spectra['info']['experiment']; - $nucleus = $spectra['info']['nucleus']; - if (is_array($nucleus)) { - $nucleus = implode('-', $nucleus); - } - array_push($type, $experiment.' - '.$nucleus); - } - $pathsMatch = false; - } - } - if (count($spectrum) > 0) { - $_nmriumJSON['data']['spectra'] = $spectrum; - $_nmrium = $dataset->nmrium; - if ($_nmrium) { - $_nmrium->nmrium_info = json_encode($_nmriumJSON, JSON_UNESCAPED_UNICODE); - $dataset->has_nmrium = true; - $_nmrium->save(); - } else { - $_nmrium = NMRium::create([ - 'nmrium_info' => json_encode($_nmriumJSON, JSON_UNESCAPED_UNICODE), - ]); - $dataset->nmrium()->save($_nmrium); - $dataset->has_nmrium = true; - } - } - $uType = array_unique($type); - if (count($uType) == 1) { - $dataset->type = $uType[0]; - } - $dataset->save(); - } - } - } - } - } catch (Exception $e) { - echo 'Caught exception: ', $e->getMessage(), "\n"; - } - - } - } - } - - public function processSpectra($url) - { - $url = urlencode($url); - $response = Http::timeout(300)->post('https://nodejs.nmrxiv.org/spectra-parser', [ - 'urls' => [$url], - 'snapshot' => false, - ]); - - return $response->json(); - } -} diff --git a/app/Console/Commands/ManageFiles.php b/app/Console/Commands/ManageFiles.php index 7ae4d64d..d5f3b6c7 100644 --- a/app/Console/Commands/ManageFiles.php +++ b/app/Console/Commands/ManageFiles.php @@ -43,7 +43,7 @@ public function handle(): void public function processFiles($path) { - $listing = Storage::disk(env('FILESYSTEM_DRIVER'))->listContents($path, true); + $listing = Storage::disk(config('filesystems.default'))->listContents($path, true); foreach ($listing as $item) { $path = $item->path(); if ($item instanceof \League\Flysystem\FileAttributes) { diff --git a/app/Console/Commands/PublishReleasedProjects.php b/app/Console/Commands/PublishReleasedProjects.php index a91c0602..a69b3092 100644 --- a/app/Console/Commands/PublishReleasedProjects.php +++ b/app/Console/Commands/PublishReleasedProjects.php @@ -32,21 +32,28 @@ class PublishReleasedProjects extends Command public function handle(PublishProject $publisher): int { return DB::transaction(function () use ($publisher) { + $publishedCount = 0; $projects = Project::where([ ['is_public', false], ['release_date', 'IS NOT', null], ])->get(); + foreach ($projects as $project) { $release_date = Carbon::parse($project->release_date); if ($release_date->isPast()) { if (! is_null($project->doi) && ! $project->is_archived) { - // echo($project->identifier); - // echo("\r\n"); + echo $project->identifier; + echo "\r\n"; $publisher->publish($project); Notification::send($project->owner, new DraftProcessedNotification($project)); + $publishedCount++; } } } + + $this->info("Published {$publishedCount} projects."); + + return Command::SUCCESS; }); } } diff --git a/app/Console/Commands/QueueMetadataExtractionBagitGenerationJobs.php b/app/Console/Commands/QueueMetadataExtractionBagitGenerationJobs.php new file mode 100644 index 00000000..78dc66b6 --- /dev/null +++ b/app/Console/Commands/QueueMetadataExtractionBagitGenerationJobs.php @@ -0,0 +1,126 @@ +option('fresh')) { + if ($this->confirm('This will reset all BagIt status for all studies. Continue?', false)) { + Study::query()->update([ + 'metadata_bagit_generation_status' => null, + 'metadata_bagit_generation_logs' => null, + ]); + $this->info('✓ Cleared all existing BagIt status data'); + } else { + return self::SUCCESS; + } + } + + $query = Study::query() + ->where('has_nmrium', true) + ->where('is_public', true) + ->whereNotNull('download_url'); + + // Filter by specific study IDs if provided + if ($ids = $this->option('ids')) { + $studyIds = array_map('trim', explode(',', $ids)); + $query->whereIn('id', $studyIds); + $this->info('Processing '.count($studyIds).' specific study IDs...'); + } + + if ($this->option('retry-failed')) { + // Get failed study IDs from database + $failedStudies = Study::where('metadata_bagit_generation_status', 'failed')->get(); + + if ($failedStudies->isEmpty()) { + $this->warn('No failed jobs to retry.'); + + return self::SUCCESS; + } + + $query->whereIn('id', $failedStudies->pluck('id')); + $this->info('Retrying '.$failedStudies->count().' failed jobs...'); + + // Reset status to pending + Study::where('metadata_bagit_generation_status', 'failed') + ->whereIn('id', $failedStudies->pluck('id')) + ->update(['metadata_bagit_generation_status' => 'pending']); + } elseif (! $this->option('ids')) { + // Only exclude already processed studies when not targeting specific IDs + $query->where(function ($q) { + $q->whereNull('metadata_bagit_generation_status') + ->orWhereIn('metadata_bagit_generation_status', ['failed']); + }); + } + + if ($limit = $this->option('limit')) { + $query->limit((int) $limit); + } + + $studies = $query->get(); + + if ($studies->isEmpty()) { + $this->warn('No eligible studies found to process.'); + + return self::SUCCESS; + } + + $this->info("Found {$studies->count()} studies to process"); + + $bar = $this->output->createProgressBar($studies->count()); + $bar->setFormat('verbose'); + + $jobsDispatched = 0; + + foreach ($studies as $study) { + // Mark as pending with queued timestamp + $study->update([ + 'metadata_bagit_generation_status' => 'pending', + 'metadata_bagit_generation_logs' => [ + 'queued_at' => now()->toIso8601String(), + 'study_identifier' => str_replace('NMRXIV:', '', $study->identifier), + ], + ]); + + // Dispatch job to queue + ProcessMetadataExtractionBagitGenerationJob::dispatch($study->id); + + $jobsDispatched++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info("✓ Successfully dispatched {$jobsDispatched} jobs to the queue"); + + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/SanitizeMolecules.php b/app/Console/Commands/SanitizeMolecules.php index ad6313f9..485ac8fa 100644 --- a/app/Console/Commands/SanitizeMolecules.php +++ b/app/Console/Commands/SanitizeMolecules.php @@ -3,8 +3,13 @@ namespace App\Console\Commands; use App\Models\Molecule; +use App\Services\CAS\CASService; use Illuminate\Console\Command; +use Illuminate\Http\Client\ConnectionException; +use Illuminate\Http\Client\RequestException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; class SanitizeMolecules extends Command { @@ -13,118 +18,383 @@ class SanitizeMolecules extends Command * * @var string */ - protected $signature = 'nmrxiv:molecules-clean'; + protected $signature = 'nmrxiv:molecules-clean + {--limit= : Limit the number of molecules to process} + {--force : Skip confirmation prompt}'; /** * The console command description. * * @var string */ - protected $description = 'Sanitize molecules'; + protected $description = 'Sanitize and enrich molecule data with PubChem properties, CAS numbers, and standardized structures'; + + /** + * Statistics for tracking progress + */ + private int $processedCount = 0; + + private int $updatedCount = 0; + + private int $errorCount = 0; + + /** + * API rate limiting delay in microseconds (500ms) + */ + private const API_DELAY_MICROSECONDS = 500000; + + public function __construct( + private CASService $casService + ) { + parent::__construct(); + } /** * Execute the console command. */ - public function handle(): void + public function handle(): int { - $molecules = Molecule::all(); - - foreach ($molecules as $molecule) { - echo $molecule->id; - echo "\r\n"; - $inchi = $molecule->standard_inchi; - if ($inchi) { - $data = $this->fetchPubChemIUPACProperties($inchi); - $molecule->synonyms = $data['synonyms']; - $molecule->iupac_name = (array_key_exists('IUPACName', $data['properties']) ? $data['properties']['IUPACName'] : $molecule->iupac_name); - $molecule->molecular_formula = (array_key_exists('MolecularFormula', $data['properties']) ? $data['properties']['MolecularFormula'] : $molecule->molecular_formula); - $molecule->molecular_weight = (array_key_exists('MolecularWeight', $data['properties']) ? $data['properties']['MolecularWeight'] : $molecule->molecular_weight); - $molecule->Save(); - } - if (! $molecule->canonical_smiles) { - echo $molecule->id; - echo "\r\n"; - $molecule->sdf = ' - '.$molecule->sdf; - $standardisedMOL = $this->standardizeMolecule($molecule->sdf); - $molecule->canonical_smiles = array_key_exists('canonical_smiles', $standardisedMOL) ? $standardisedMOL['canonical_smiles'] : null; - $molecule->standard_inchi = array_key_exists('inchi', $standardisedMOL) ? $standardisedMOL['inchi'] : null; - $molecule->inchi_key = array_key_exists('canonicalinchikey_smiles', $standardisedMOL) ? $standardisedMOL['inchikey'] : null; - $molecule->save(); + $totalMolecules = $this->option('limit') + ? min((int) $this->option('limit'), Molecule::count()) + : Molecule::count(); + + $this->info("Processing {$totalMolecules} molecules..."); + + if ($totalMolecules === 0) { + $this->info('No molecules found to process.'); + + return self::SUCCESS; + } + + if (! $this->option('force') && ! $this->confirm('Do you want to continue?', true)) { + $this->info('Operation cancelled.'); + + return self::SUCCESS; + } + + $progressBar = $this->output->createProgressBar($totalMolecules); + $progressBar->start(); + + $query = Molecule::query(); + + if ($this->option('limit')) { + $query->limit((int) $this->option('limit')); + } + + $query->chunk(100, function ($molecules) use ($progressBar) { + foreach ($molecules as $molecule) { + $this->processMolecule($molecule); + $progressBar->advance(); } - if ($molecule->canonical_smiles) { - $cas = $this->fetchCAS($molecule->canonical_smiles); - $molecule->cas = $cas; + }); + + $progressBar->finish(); + $this->newLine(2); + $this->displayStatistics(); + + return self::SUCCESS; + } + + /** + * Process a single molecule + */ + protected function processMolecule(Molecule $molecule): void + { + try { + DB::beginTransaction(); + + $updated = false; + + // Fetch PubChem properties if standard InChI is available + if ($molecule->standard_inchi) { + $updated = $this->enrichWithPubChemData($molecule) || $updated; + } + + // Standardize molecule structure if canonical SMILES is missing + if (! $molecule->canonical_smiles && $molecule->sdf) { + $updated = $this->standardizeMoleculeStructure($molecule) || $updated; + } + + // Fetch CAS number if canonical SMILES is available + if ($molecule->canonical_smiles && ! $molecule->cas) { + $updated = $this->enrichWithCASNumber($molecule) || $updated; + } + + // Only save if something was updated + if ($updated) { $molecule->save(); + $this->updatedCount++; } + + $this->processedCount++; + + DB::commit(); + } catch (\Exception $e) { + DB::rollBack(); + $this->errorCount++; + + Log::error('Failed to process molecule', [ + 'molecule_id' => $molecule->id, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + $this->warn("Failed to process molecule ID {$molecule->id}: ".$e->getMessage()); } + + // Rate limiting delay + usleep(self::API_DELAY_MICROSECONDS); } - protected function fetchPubChemIUPACProperties($inchi) + /** + * Enrich molecule with PubChem data + */ + protected function enrichWithPubChemData(Molecule $molecule): bool { - $pubchemBase = rtrim(config('services.pubchem.base_url'), '/'); - $pubchemPug = trim(config('services.pubchem.pug_rest_path'), '/'); - $cid_response = Http::get($pubchemBase.'/'.$pubchemPug.'/compound/inchi/cids/JSON', [ - 'inchi' => $inchi, - ]); - $cid_data = $cid_response->json(); - $cid = null; - if (array_key_exists('IdentifierList', $cid_data)) { - $cids = $cid_data['IdentifierList']['CID']; - $cid = $cids[0]; - } - $synonyms = ''; - $properties = []; - if ($cid) { - $synonyms_response = Http::get($pubchemBase.'/'.$pubchemPug.'/compound/cid/'.$cid.'/Synonyms/JSON'); - $synonyms_data = $synonyms_response->json(); - if (! array_key_exists('Fault', $synonyms_data)) { - $synonyms = implode(',', $synonyms_data['InformationList']['Information'][0]['Synonym']); + try { + $data = $this->fetchPubChemIUPACProperties($molecule->standard_inchi); + + if (empty($data['synonyms']) && empty($data['properties'])) { + return false; + } + + $updated = false; + + if (! empty($data['synonyms']) && $molecule->synonyms !== $data['synonyms']) { + $molecule->synonyms = $data['synonyms']; + $updated = true; + } + + if (! empty($data['properties']['IUPACName']) && ! $molecule->iupac_name) { + $molecule->iupac_name = $data['properties']['IUPACName']; + $updated = true; + } + + if (! empty($data['properties']['MolecularFormula']) && ! $molecule->molecular_formula) { + $molecule->molecular_formula = $data['properties']['MolecularFormula']; + $updated = true; } - $properties_response = Http::get($pubchemBase.'/'.$pubchemPug.'/compound/cid/'.$cid.'/property/IUPACName,MolecularWeight,MolecularFormula/JSON'); - $properties_data = $properties_response->json(); - if (! array_key_exists('Fault', $properties_data)) { - $properties = $properties_data['PropertyTable']['Properties'][0]; + if (! empty($data['properties']['MolecularWeight']) && ! $molecule->molecular_weight) { + $molecule->molecular_weight = (float) $data['properties']['MolecularWeight']; + $updated = true; } + + return $updated; + } catch (\Exception $e) { + Log::warning('Failed to fetch PubChem data', [ + 'molecule_id' => $molecule->id, + 'inchi' => $molecule->standard_inchi, + 'error' => $e->getMessage(), + ]); + + return false; } + } - return [ - 'synonyms' => $synonyms, - 'properties' => $properties, - ]; + /** + * Standardize molecule structure + */ + protected function standardizeMoleculeStructure(Molecule $molecule): bool + { + try { + $standardisedMOL = $this->standardizeMolecule($molecule->sdf); + + if (empty($standardisedMOL)) { + return false; + } + + $updated = false; + + if (! empty($standardisedMOL['canonical_smiles'])) { + $molecule->canonical_smiles = $standardisedMOL['canonical_smiles']; + $updated = true; + } + + if (! empty($standardisedMOL['inchi'])) { + $molecule->standard_inchi = $standardisedMOL['inchi']; + $updated = true; + } + + if (! empty($standardisedMOL['inchikey'])) { + $molecule->inchi_key = $standardisedMOL['inchikey']; + $updated = true; + } + + return $updated; + } catch (\Exception $e) { + Log::warning('Failed to standardize molecule', [ + 'molecule_id' => $molecule->id, + 'error' => $e->getMessage(), + ]); + + return false; + } } - protected function fetchCAS($smiles) + /** + * Enrich molecule with CAS number + */ + protected function enrichWithCASNumber(Molecule $molecule): bool { try { - $ccBase = rtrim(config('services.common_chemistry.base_url'), '/'); - $ccApi = trim(config('services.common_chemistry.api_path'), '/'); - $response = Http::get($ccBase.'/'.$ccApi.'/search', [ - 'q' => $smiles, + $cas = $this->fetchCAS($molecule->canonical_smiles); + + if ($cas) { + $molecule->cas = $cas; + + return true; + } + + return false; + } catch (\Exception $e) { + Log::warning('Failed to fetch CAS number', [ + 'molecule_id' => $molecule->id, + 'smiles' => $molecule->canonical_smiles, + 'error' => $e->getMessage(), ]); - if (! $response->failed()) { - $data = $response->json(); - if ($data['count'] > 0) { - $cas = $data['results'][0]['rn']; - return $cas; + return false; + } + } + + /** + * Fetch PubChem IUPAC properties + * + * @return array{synonyms: string, properties: array} + */ + protected function fetchPubChemIUPACProperties(string $inchi): array + { + $pubchemBase = rtrim(config('services.pubchem.base_url'), '/'); + $pubchemPug = trim(config('services.pubchem.pug_rest_path'), '/'); + + try { + // Fetch CID from InChI with retry + $cidResponse = Http::retry(3, 1000) + ->timeout(30) + ->get($pubchemBase.'/'.$pubchemPug.'/compound/inchi/cids/JSON', [ + 'inchi' => $inchi, + ]); + + $cidResponse->throw(); + $cidData = $cidResponse->json(); + + if (! isset($cidData['IdentifierList']['CID'][0])) { + return ['synonyms' => '', 'properties' => []]; + } + + $cid = $cidData['IdentifierList']['CID'][0]; + + // Fetch synonyms + $synonyms = ''; + try { + $synonymsResponse = Http::retry(3, 1000) + ->timeout(30) + ->get($pubchemBase.'/'.$pubchemPug.'/compound/cid/'.$cid.'/Synonyms/JSON'); + + $synonymsResponse->throw(); + $synonymsData = $synonymsResponse->json(); + + if (isset($synonymsData['InformationList']['Information'][0]['Synonym'])) { + $synonyms = implode(',', $synonymsData['InformationList']['Information'][0]['Synonym']); } + } catch (RequestException $e) { + Log::debug('Failed to fetch PubChem synonyms', ['cid' => $cid, 'error' => $e->getMessage()]); } - } catch (\Illuminate\Http\Client\ConnectionException $e) { - echo 'timed out: '.$smiles; + + // Fetch properties + $properties = []; + try { + $propertiesResponse = Http::retry(3, 1000) + ->timeout(30) + ->get($pubchemBase.'/'.$pubchemPug.'/compound/cid/'.$cid.'/property/IUPACName,MolecularWeight,MolecularFormula/JSON'); + + $propertiesResponse->throw(); + $propertiesData = $propertiesResponse->json(); + + if (isset($propertiesData['PropertyTable']['Properties'][0])) { + $properties = $propertiesData['PropertyTable']['Properties'][0]; + } + } catch (RequestException $e) { + Log::debug('Failed to fetch PubChem properties', ['cid' => $cid, 'error' => $e->getMessage()]); + } + + return [ + 'synonyms' => $synonyms, + 'properties' => $properties, + ]; + } catch (RequestException $e) { + throw new \RuntimeException("Failed to fetch PubChem data for InChI: {$e->getMessage()}", 0, $e); + } + } + + /** + * Fetch CAS number from SMILES + */ + protected function fetchCAS(string $smiles): ?string + { + if (! config('services.cas.api_token')) { + return null; + } + + try { + return $this->casService->searchCASBySmiles($smiles); + } catch (\Exception $e) { + Log::debug('Failed to fetch CAS', [ + 'smiles' => $smiles, + 'error' => $e->getMessage(), + ]); + + return null; } } - protected function standardizeMolecule($mol) + /** + * Standardize molecule structure + * + * @return array|null + */ + protected function standardizeMolecule(string $mol): ?array { try { $stdUrl = config('services.chemistry_standardize.url'); - $response = Http::post($stdUrl, $mol); + + if (! $stdUrl) { + return null; + } + + $response = Http::retry(3, 1000) + ->timeout(30) + ->post($stdUrl, $mol); + + $response->throw(); return $response->json(); - } catch (\Illuminate\Http\Client\ConnectionException $e) { - echo 'timed out: '.$mol; + } catch (ConnectionException $e) { + Log::warning('Chemistry standardize API connection failed', ['error' => $e->getMessage()]); + + return null; + } catch (RequestException $e) { + Log::warning('Chemistry standardize API request failed', ['error' => $e->getMessage()]); + + return null; } } + + /** + * Display processing statistics + */ + protected function displayStatistics(): void + { + $this->info('Molecule sanitization completed!'); + $this->newLine(); + $this->table( + ['Metric', 'Count'], + [ + ['Processed', $this->processedCount], + ['Updated', $this->updatedCount], + ['Errors', $this->errorCount], + ] + ); + } } diff --git a/app/Console/Commands/SanitizeProjects.php b/app/Console/Commands/SanitizeProjects.php index 1a4370d4..f07c8006 100644 --- a/app/Console/Commands/SanitizeProjects.php +++ b/app/Console/Commands/SanitizeProjects.php @@ -93,7 +93,7 @@ public function cleanProjects() $user_id = $project->owner->id; $team_id = $project->team_id; $id = Str::uuid(); - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = preg_replace( '~//+~', '/', @@ -169,7 +169,7 @@ public function cleanDrafts() $user_id = $project->owner->id; $team_id = $project->team_id; $id = Str::uuid(); - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = preg_replace( '~//+~', '/', @@ -362,7 +362,7 @@ public function updateFilesStatus() } if ($fsObject->path && $fsObject->type == 'file') { - $exists = Storage::disk(env('FILESYSTEM_DRIVER'))->exists($fsObject->path); + $exists = Storage::disk(config('filesystems.default'))->exists($fsObject->path); if (! $exists) { $fsObject->status = 'missing'; } else { @@ -382,7 +382,7 @@ public function updateFilePaths() if (! $fsObject->path) { $project = $fsObject->project; if ($project) { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = preg_replace( '~//+~', '/', @@ -431,7 +431,7 @@ public function moveFolder($fsObject, $project, $draft) if ($fsObjectChild->type == 'file') { $newFilePath = '/'.$draft->path.$fsObjectChild->relative_url; if ($fsObjectChild->path && $newFilePath && $fsObjectChild->path != $newFilePath) { - Storage::disk(env('FILESYSTEM_DRIVER'))->move($fsObjectChild->path, $newFilePath); + Storage::disk(config('filesystems.default'))->move($fsObjectChild->path, $newFilePath); $fsObjectChild->path = $newFilePath; $fsObjectChild->draft_id = $draft->id; $fsObjectChild->save(); diff --git a/app/Console/Commands/UnpublishProjects.php b/app/Console/Commands/UnpublishProjects.php index 1a653c5e..88bce732 100644 --- a/app/Console/Commands/UnpublishProjects.php +++ b/app/Console/Commands/UnpublishProjects.php @@ -41,7 +41,7 @@ public function handle(UnPublishProject $unpublisher): int $user_id = $project->owner->id; $team_id = $project->team_id; $id = Str::uuid(); - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = preg_replace( '~//+~', '/', @@ -102,7 +102,7 @@ public function moveFolder($fsObject, $project, $draft) if ($fsObjectChild->type == 'file') { $newFilePath = '/'.$draft->path.$fsObjectChild->relative_url; if ($fsObjectChild->path && $newFilePath && $fsObjectChild->path != $newFilePath) { - Storage::disk(env('FILESYSTEM_DRIVER'))->move($fsObjectChild->path, $newFilePath); + Storage::disk(config('filesystems.default'))->move($fsObjectChild->path, $newFilePath); $fsObjectChild->path = $newFilePath; $fsObjectChild->draft_id = $draft->id; $fsObjectChild->save(); diff --git a/app/Console/Commands/UpdateProjectStatuses.php b/app/Console/Commands/UpdateProjectStatuses.php new file mode 100644 index 00000000..d00badb7 --- /dev/null +++ b/app/Console/Commands/UpdateProjectStatuses.php @@ -0,0 +1,126 @@ +option('dry-run'); + + if ($isDryRun) { + $this->info('🔍 DRY RUN MODE - No changes will be made'); + } else { + $this->info('🚀 LIVE MODE - Project statuses will be updated'); + } + + $this->newLine(); + + return DB::transaction(function () use ($isDryRun) { + $now = Carbon::now(); + $updatedCount = 0; + + // Get all projects with their current data + $projects = Project::all(); + $this->info("📊 Found {$projects->count()} total projects to analyze"); + + // Track updates by status type + $statusCounts = [ + 'deleted' => 0, + 'archived' => 0, + 'embargo' => 0, + 'published' => 0, + 'draft' => 0, + ]; + + foreach ($projects as $project) { + $newStatus = $this->determineProjectStatus($project, $now); + $oldStatus = $project->status; + + if ($newStatus !== $oldStatus) { + $this->info("📝 Project {$project->id} ({$project->name}): '{$oldStatus}' → '{$newStatus}'"); + + if (! $isDryRun) { + $project->update(['status' => $newStatus]); + } + + $updatedCount++; + $statusCounts[$newStatus]++; + } else { + $this->line("✓ Project {$project->id} already has correct status: '{$newStatus}'"); + } + } + + $this->newLine(); + $this->info('📈 Status Distribution:'); + foreach ($statusCounts as $status => $count) { + if ($count > 0) { + $this->line(" • {$status}: {$count} projects"); + } + } + + $this->newLine(); + if ($isDryRun) { + $this->info("🔍 DRY RUN COMPLETE: {$updatedCount} projects would be updated"); + $this->info('💡 Run without --dry-run to apply changes'); + } else { + $this->info("✅ SUCCESS: Updated {$updatedCount} project statuses"); + } + + return 0; + }); + } + + /** + * Determine the correct status for a project based on its flags + */ + private function determineProjectStatus(Project $project, Carbon $now): string + { + // Priority order based on business logic: + + // 1. If deleted, status is 'deleted' + if ($project->is_deleted) { + return 'deleted'; + } + + // 2. If archived, status is 'archived' + if ($project->is_archived) { + return 'archived'; + } + + // 3. If public, status is 'published' + if ($project->is_public) { + return 'published'; + } + + // 4. If has future release_date, status is 'embargo' + if ($project->release_date && Carbon::parse($project->release_date)->isAfter($now)) { + return 'embargo'; + } + + // 5. Default to 'draft' for everything else + return 'draft'; + } +} diff --git a/app/Events/AddingProjectMember.php b/app/Events/AddingProjectMember.php index 580791b0..62dea8fa 100644 --- a/app/Events/AddingProjectMember.php +++ b/app/Events/AddingProjectMember.php @@ -11,14 +11,23 @@ class AddingProjectMember { use Dispatchable, InteractsWithSockets, SerializesModels; + /** + * The project instance. + */ + public $project; + + /** + * The user instance. + */ + public $user; + /** * Create a new event instance. - * - * @return void */ - public function __construct() + public function __construct($project, $user) { - // + $this->project = $project; + $this->user = $user; } /** diff --git a/app/Events/ProjectMemberAdded.php b/app/Events/ProjectMemberAdded.php index 5958d717..54e5e9a2 100644 --- a/app/Events/ProjectMemberAdded.php +++ b/app/Events/ProjectMemberAdded.php @@ -11,14 +11,23 @@ class ProjectMemberAdded { use Dispatchable, InteractsWithSockets, SerializesModels; + /** + * The project instance. + */ + public $project; + + /** + * The user instance. + */ + public $user; + /** * Create a new event instance. - * - * @return void */ - public function __construct() + public function __construct($project, $user) { - // + $this->project = $project; + $this->user = $user; } /** diff --git a/app/Helper.php b/app/Helper.php index b3227949..347390c7 100644 --- a/app/Helper.php +++ b/app/Helper.php @@ -1,6 +1,7 @@ firstOrFail(); } elseif ($namespace == 'Dataset') { $model = Dataset::where([['identifier', $id]])->firstOrFail(); + } elseif ($namespace == 'Molecule') { + $model = Molecule::where([['identifier', $id]])->firstOrFail(); } return [ diff --git a/app/Http/Controllers/API/Auth/LoginController.php b/app/Http/Controllers/API/Auth/LoginController.php index 7964a073..8ea21f9d 100644 --- a/app/Http/Controllers/API/Auth/LoginController.php +++ b/app/Http/Controllers/API/Auth/LoginController.php @@ -88,21 +88,6 @@ class LoginController extends Controller * ), * * @OA\Response( - * response=403, - * description="Account not verified - Email verification required", - * - * @OA\JsonContent( - * - * @OA\Property( - * property="message", - * type="string", - * description="Error message indicating account verification status", - * example="Account is not yet verified. Please verify your email address by clicking on the link we just emailed to you." - * ) - * ) - * ), - * - * @OA\Response( * response=422, * description="Validation error - Invalid input data", * @@ -174,6 +159,10 @@ class LoginController extends Controller */ public function login(Request $request): JsonResponse { + $request->validate([ + 'email' => ['required', 'email'], + 'password' => ['required', 'string'], + ]); if (! Auth::attempt($request->only('email', 'password'))) { return response()->json([ diff --git a/app/Http/Controllers/API/Auth/RegisterController.php b/app/Http/Controllers/API/Auth/RegisterController.php index 12c6ffc4..6ab550c9 100644 --- a/app/Http/Controllers/API/Auth/RegisterController.php +++ b/app/Http/Controllers/API/Auth/RegisterController.php @@ -276,7 +276,7 @@ public function register(Request $request) 'status' => false, 'message' => 'validation error', 'errors' => $validateUserDetails->errors(), - ], 401); + ], 422); } $authUser = auth('sanctum')->user(); diff --git a/app/Http/Controllers/API/Auth/VerificationController.php b/app/Http/Controllers/API/Auth/VerificationController.php index 4c6e944c..b9548c54 100644 --- a/app/Http/Controllers/API/Auth/VerificationController.php +++ b/app/Http/Controllers/API/Auth/VerificationController.php @@ -144,7 +144,9 @@ public function verify($user_id, Request $request) } if ($request->user() && $request->user()->getKey() != $user_id) { - Auth::logout(); + if (method_exists(Auth::guard(), 'logout')) { + Auth::logout(); + } throw new AuthorizationException; } diff --git a/app/Http/Controllers/API/DataController.php b/app/Http/Controllers/API/DataController.php index 15c0f13a..84e43236 100644 --- a/app/Http/Controllers/API/DataController.php +++ b/app/Http/Controllers/API/DataController.php @@ -56,7 +56,7 @@ class DataController extends Controller * in="query", * description="Sort field with optional direction prefix (-created_at for descending)", * - * @OA\Schema(type="string", enum={"created_at", "-created_at", "identifier", "-identifier", "owner.email", "-owner.email"}, default="-created_at") + * @OA\Schema(type="string", enum={"created_at", "-created_at", "identifier", "-identifier"}, default="-created_at") * ), * * @OA\Parameter( @@ -76,14 +76,6 @@ class DataController extends Controller * ), * * @OA\Parameter( - * name="filter[owner.email]", - * in="query", - * description="Filter by data owner email", - * - * @OA\Schema(type="string", format="email", example="researcher@university.edu") - * ), - * - * @OA\Parameter( * name="filter[doi]", * in="query", * description="Filter by Digital Object Identifier", @@ -259,8 +251,8 @@ public function all(Request $request, $model) $per_page = \Request::get('per_page') ?: 100; $defaultSort = '-created_at'; - $allowedSorts = ['created_at', 'identifier', 'owner.email']; - $allowedFilters = ['name', 'created_at', 'identifier', 'owner.email', 'doi']; + $allowedSorts = ['created_at', 'identifier']; + $allowedFilters = ['name', 'created_at', 'identifier', 'doi']; if ($model === 'projects') { return ProjectResource::collection( QueryBuilder::for(Project::class) diff --git a/app/Http/Controllers/API/ELNController.php b/app/Http/Controllers/API/ELNController.php index 6af9947d..b9b2f772 100644 --- a/app/Http/Controllers/API/ELNController.php +++ b/app/Http/Controllers/API/ELNController.php @@ -21,6 +21,7 @@ class ELNController extends Controller */ const SUPPORTED_ELNS = [ 'chemotion', + 'nobs', ]; /** @@ -29,7 +30,7 @@ class ELNController extends Controller * operationId="uploadELNData", * tags={"ELN Submission"}, * summary="Upload and process data from Electronic Lab Notebook (ELN) systems", - * description="Creates or updates a draft with data from external ELN systems. Currently supports Chemotion. Processes ZIP files containing experimental data and extracts them to organized folder structure.", + * description="Creates or updates a draft with data from external ELN systems. Currently supports Chemotion and NoBs. Processes ZIP files containing experimental data and extracts them to organized folder structure.", * security={{"sanctum": {}}}, * * @OA\Parameter( @@ -40,7 +41,7 @@ class ELNController extends Controller * * @OA\Schema( * type="string", - * enum={"chemotion"}, + * enum={"chemotion", "nobs"}, * example="chemotion" * ) * ), @@ -170,7 +171,7 @@ class ELNController extends Controller * type="array", * * @OA\Items(type="string"), - * example={"chemotion"} + * example={"chemotion", "nobs"} * ) * ) * ), @@ -355,7 +356,7 @@ public function upload($eln, Request $request) * * @OA\Schema( * type="string", - * enum={"chemotion"}, + * enum={"chemotion", "nobs"}, * example="chemotion" * ) * ), @@ -497,7 +498,7 @@ public function upload($eln, Request $request) * type="array", * * @OA\Items(type="string"), - * example={"chemotion"} + * example={"chemotion", "nobs"} * ) * ) * ), diff --git a/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasController.php b/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasController.php index c941d378..3e0bf093 100644 --- a/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasController.php +++ b/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasController.php @@ -577,7 +577,7 @@ public function getSample($study) $sampleSchema['dct:conformsTo'] = BioschemasHelper::conformsTo(['https://bioschemas.org/types/ChemicalSubstance/0.3-RELEASE-2019_09_02']); $sampleSchema->name($sample->name); $sampleSchema->description($sample->description); - $sampleSchema->url(env('APP_URL').'/'.explode(':', $study->identifier ? $study->identifier : ':')[1]); + $sampleSchema->url(config('app.url').'/'.explode(':', $study->identifier ? $study->identifier : ':')[1]); $sampleSchema->hasBioChemEntityPart($this->prepareMoleculesSchemas($sample)); return $sampleSchema; @@ -791,7 +791,7 @@ public function datasetLite($dataset) $datasetSchema->description($dataset->description); $datasetSchema->keywords($nmriumInfo[0]); $datasetSchema->license($dataset->study->license->url); - $datasetSchema->url(env('APP_URL').'/'.explode(':', $dataset->identifier ? $dataset->identifier : ':')[1]); + $datasetSchema->url(config('app.url').'/'.explode(':', $dataset->identifier ? $dataset->identifier : ':')[1]); $datasetSchema->dateCreated($dataset->created_at ? $dataset->created_at->toISOString() : null); $datasetSchema->dateModified($dataset->updated_at ? $dataset->updated_at->toISOString() : null); $datasetSchema->datePublished($dataset->release_date ? Carbon::parse($dataset->release_date)->toISOString() : null); @@ -846,7 +846,7 @@ public function studyLite($study) $studySchema->description($study->description); $studySchema->keywords(BioschemasHelper::getTags($study)); $studySchema->license($study->license->url); - $studySchema->url(env('APP_URL').'/'.explode(':', $study->identifier ? $study->identifier : ':')[1]); + $studySchema->url(config('app.url').'/'.explode(':', $study->identifier ? $study->identifier : ':')[1]); $studySchema->dateCreated($study->created_at ? $study->created_at->toISOString() : null); $studySchema->dateModified($study->updated_at ? $study->updated_at->toISOString() : null); $studySchema->datePublished($study->release_date ? Carbon::parse($study->release_date)->toISOString() : null); @@ -894,7 +894,7 @@ public function projectLite($project) $projectSchema->keywords(BioschemasHelper::getTags($project)); $projectSchema->license($project->license->url); $projectSchema->publisher(BioschemasHelper::preparePublisher()); - $projectSchema->url(env('APP_URL').'/'.explode(':', $project->identifier ? $project->identifier : ':')[1]); + $projectSchema->url(config('app.url').'/'.explode(':', $project->identifier ? $project->identifier : ':')[1]); $projectSchema->dateCreated($project->created_at ? $project->created_at->toISOString() : null); $projectSchema->dateModified($project->updated_at ? $project->updated_at->toISOString() : null); $projectSchema->datePublished($project->release_date ? Carbon::parse($project->release_date)->toISOString() : null); diff --git a/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasHelper.php b/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasHelper.php index bdb4710c..4b772ee4 100644 --- a/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasHelper.php +++ b/app/Http/Controllers/API/Schemas/Bioschemas/BioschemasHelper.php @@ -146,7 +146,7 @@ public static function prepareCitations($model) */ public static function prepareDataDownload($dataset) { - $url = env('APP_URL'); + $url = config('app.url'); $user = $dataset->owner->username; if (property_exists($dataset, 'project')) { $slug = $dataset->project->slug; @@ -175,8 +175,8 @@ public static function prepareDataDownload($dataset) public static function preparePublisher() { $publisherSchema = Schema::Organization(); - $publisherSchema->name(env('APP_NAME')); - $publisherSchema->url(env('APP_URL')); + $publisherSchema->name(config('app.name')); + $publisherSchema->url(config('app.url')); return $publisherSchema; } @@ -192,8 +192,8 @@ public static function preparePublisher() public static function prepareDataCatalogLite() { $dataCatalogSchema = Schema::DataCatalog(); - $dataCatalogSchema->name(env('APP_NAME')); - $dataCatalogSchema->url(env('APP_URL')); + $dataCatalogSchema->name(config('app.name')); + $dataCatalogSchema->url(config('app.url')); return $dataCatalogSchema; } diff --git a/app/Http/Controllers/API/Schemas/Bioschemas/DataCatalogController.php b/app/Http/Controllers/API/Schemas/Bioschemas/DataCatalogController.php index cda487f8..d20c39fd 100644 --- a/app/Http/Controllers/API/Schemas/Bioschemas/DataCatalogController.php +++ b/app/Http/Controllers/API/Schemas/Bioschemas/DataCatalogController.php @@ -254,13 +254,13 @@ public function dataCatalogSchema(Request $request) $contributors = $this->prepareContributors(); $nmrXivProvider = Schema::Organization(); - $nmrXivProvider->name(Config::get('schemas.bioschema.provider')); - $nmrXivProvider->url(Config::get('schemas.bioschema.provider_url')); + $nmrXivProvider->name(Config::get('schemas.bioschemas.provider')); + $nmrXivProvider->url(Config::get('schemas.bioschemas.provider_url')); $dataCatalogSchema = Schema::DataCatalog(); - $dataCatalogSchema['@id'] = url(Config::get('app.url')); + $dataCatalogSchema['@id'] = Config::get('app.url'); $dataCatalogSchema['dct:conformsTo'] = BioschemasHelper::conformsTo(['https://schema.org/DataCatalog']); - $dataCatalogSchema->description(env('APP_DESCRIPTION')); + $dataCatalogSchema->description(Config::get('app.description', 'NMRXIV is an open-access preprint repository for sharing and discovering nuclear magnetic resonance (NMR) spectroscopy data.')); $dataCatalogSchema->keywords($keywords); $dataCatalogSchema->name(Config::get('app.name')); $dataCatalogSchema->provider($nmrXivProvider); diff --git a/app/Http/Controllers/Admin/UsersController.php b/app/Http/Controllers/Admin/UsersController.php index 131469a0..b351a275 100644 --- a/app/Http/Controllers/Admin/UsersController.php +++ b/app/Http/Controllers/Admin/UsersController.php @@ -42,6 +42,7 @@ public function index(Request $request) 'role' => $user->getRoleNames(), 'orcid_id' => $user->orcid_id, 'affiliation' => $user->affiliation, + 'ror_id' => $user->ror_id, ]; }), 'roles' => Role::orderBy('name') @@ -97,6 +98,7 @@ public function edit(User $user) 'profile_photo_url' => $user->profile_photo_url, 'orcid_id' => $user->orcid_id, 'affiliation' => $user->affiliation, + 'ror_id' => $user->ror_id, ], ]); diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php index 022a698d..fe855738 100644 --- a/app/Http/Controllers/ApplicationController.php +++ b/app/Http/Controllers/ApplicationController.php @@ -42,7 +42,10 @@ public function resolveCompound(Request $request, $identifier) if ($model && $namespace === 'Molecule') { // Redirect to spectra page with compound parameter for now // This maintains the current compound viewing functionality - return redirect('/spectra?compound='.substr($identifier, 1)); + // Use getRawOriginal to get the numeric identifier without NMRXIV:M prefix + $compoundId = $model->getRawOriginal('identifier'); + + return redirect('/spectra?compound='.$compoundId); } else { abort(404, 'Compound not found'); } @@ -102,6 +105,7 @@ public function resolve(Request $request, $identifier) } } elseif ($namespace == 'Study') { $study = $model; + $study->load('studyAuthors'); // Eager load authors $project = $study->project; $tab = 'study'; } elseif ($namespace == 'Dataset') { @@ -147,7 +151,7 @@ public function resolve(Request $request, $identifier) } else { return Inertia::render('Public/Sample/Show', [ 'tab' => $tab, - 'study' => (new StudyResource($study))->lite(false, ['tags', 'sample', 'datasets', 'molecules', 'owner', 'license']), + 'study' => (new StudyResource($study))->lite(false, ['tags', 'sample', 'datasets', 'molecules', 'owner', 'license', 'authors']), ]); break; } diff --git a/app/Http/Controllers/CASController.php b/app/Http/Controllers/CASController.php new file mode 100644 index 00000000..e60b8197 --- /dev/null +++ b/app/Http/Controllers/CASController.php @@ -0,0 +1,45 @@ +validate([ + 'cas_rn' => 'required|string|max:20', + ]); + + $casNumber = $request->input('cas_rn'); + + // Check if API token is configured + if (! Config::get('services.cas.api_token')) { + return response()->json([ + 'error' => 'CAS Service not configured', + ], 500); + } + + try { + $data = $this->casService->getCASDetails($casNumber); + + return response()->json($data); + + } catch (\Exception $e) { + return response()->json([ + 'error' => 'Unable to retrieve CAS details. Please verify the CAS number and try again.', + ], 400); + } + } +} diff --git a/app/Http/Controllers/DatasetController.php b/app/Http/Controllers/DatasetController.php index 64afcaea..246dcf6c 100644 --- a/app/Http/Controllers/DatasetController.php +++ b/app/Http/Controllers/DatasetController.php @@ -19,11 +19,15 @@ public function publicDatasetView(Request $request, $slug) { $dataset = Dataset::where('slug', $slug)->firstOrFail(); - if ($dataset->is_public) { - return Inertia::render('Public/Dataset', [ - 'dataset' => $dataset, - ]); + if (! $dataset->is_public) { + return response()->json([ + 'message' => 'Unauthorized', + ], 401); } + + return Inertia::render('Public/Dataset', [ + 'dataset' => $dataset, + ]); } public function fetchNMRium(Request $request, Dataset $dataset) @@ -142,12 +146,12 @@ public function snapshot(Request $request, Dataset $dataset) if ($content) { if ($study->project) { $path = '/projects/'.$study->project->uuid.'/'.$study->uuid.'/'.$dataset->slug.'.svg'; - Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->put($path, $content, 'public'); + Storage::disk(config('filesystems.default_public'))->put($path, $content, 'public'); $dataset->dataset_photo_path = $path; $dataset->save(); } else { $path = '/samples/'.$study->uuid.'/'.$dataset->slug.'.svg'; - Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->put($path, $content, 'public'); + Storage::disk(config('filesystems.default_public'))->put($path, $content, 'public'); $dataset->dataset_photo_path = $path; $dataset->save(); } diff --git a/app/Http/Controllers/DownloadController.php b/app/Http/Controllers/DownloadController.php index ef3faab4..1a62737e 100644 --- a/app/Http/Controllers/DownloadController.php +++ b/app/Http/Controllers/DownloadController.php @@ -66,7 +66,7 @@ public function downloadSet(Request $request, $username, $project, $study = null $fsObj = new FileSystemObject; $fsObj->type = 'directory'; $fsObj->name = $project->slug; - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $fsObj->path = $environment.'/'.$project->uuid; $fsObj->key = $project->uuid; $fsObj->relative_url = '/'.$project->uuid; @@ -92,12 +92,12 @@ public function downloadFromProject(Request $request, $username, $project, $key $s3Client = $this->storageClient(); - $bucket = $request->input('bucket') ?: config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.bucket'); + $bucket = $request->input('bucket') ?: config('filesystems.disks.'.config('filesystems.default').'.bucket'); $s3keys = []; if ($fsObj->type == 'file') { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); if (Storage::has($path)) { array_push($s3keys, substr($fsObj->path, 1)); } @@ -168,15 +168,16 @@ function () use ($s3keys, $bucket, $fsObj) { */ protected function storageClient() { + $diskName = config('filesystems.default'); $config = [ - 'region' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.region'), + 'region' => config('filesystems.disks.'.$diskName.'.region'), 'version' => 'latest', 'use_path_style_endpoint' => true, - 'url' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), - 'endpoint' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), + 'url' => config('filesystems.disks.'.$diskName.'.endpoint'), + 'endpoint' => config('filesystems.disks.'.$diskName.'.endpoint'), 'credentials' => [ - 'key' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.key'), - 'secret' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.secret'), + 'key' => config('filesystems.disks.'.$diskName.'.key'), + 'secret' => config('filesystems.disks.'.$diskName.'.secret'), ], ]; diff --git a/app/Http/Controllers/OrcidController.php b/app/Http/Controllers/OrcidController.php new file mode 100644 index 00000000..3af51001 --- /dev/null +++ b/app/Http/Controllers/OrcidController.php @@ -0,0 +1,86 @@ +input('q'); + + if (! $query) { + return response()->json(['error' => 'Query parameter is required'], 400); + } + + try { + $response = Http::withHeaders([ + 'Accept' => 'application/json', + ])->get(config('orcid.base_url').'/search', [ + 'q' => $query, + ]); + + if ($response->failed()) { + return response()->json([ + 'error' => 'Failed to fetch ORCID search results', + 'message' => $response->json('error-desc.value') ?? 'ORCID API returned an error', + ], $response->status()); + } + + return response()->json($response->json()); + } catch (\Exception $e) { + return response()->json(['error' => 'Failed to fetch ORCID search results'], 500); + } + } + + public function person(string $orcidId): JsonResponse + { + if (! $orcidId) { + return response()->json(['error' => 'ORCID ID is required'], 400); + } + + try { + $response = Http::withHeaders([ + 'Accept' => 'application/json', + ])->get(config('orcid.base_url').'/'.$orcidId.'/person'); + + if ($response->failed()) { + return response()->json([ + 'error' => 'Failed to fetch person data', + 'message' => $response->json('error-desc.value') ?? 'ORCID API returned an error', + ], $response->status()); + } + + return response()->json($response->json()); + } catch (\Exception $e) { + return response()->json(['error' => 'Failed to fetch person data'], 500); + } + } + + public function employment(string $orcidId): JsonResponse + { + if (! $orcidId) { + return response()->json(['error' => 'ORCID ID is required'], 400); + } + + try { + $response = Http::withHeaders([ + 'Accept' => 'application/json', + ])->get(config('orcid.base_url').'/'.$orcidId.'/employments'); + + if ($response->failed()) { + return response()->json([ + 'error' => 'Failed to fetch employment data', + 'message' => $response->json('error-desc.value') ?? 'ORCID API returned an error', + ], $response->status()); + } + + return response()->json($response->json()); + } catch (\Exception $e) { + return response()->json(['error' => 'Failed to fetch employment data'], 500); + } + } +} diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 58f1f948..a8a0135f 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -199,7 +199,7 @@ public function settings(Request $request, Project $project) return Inertia::render('Project/Settings', [ 'project' => $project, - 'schema' => $environment = env('SCHEMA_VERSION', 'local'), + 'schema' => config('app.schema_version', 'beta'), 'projectPermissions' => [ 'canDeleteProject' => Gate::check('deleteProject', $project), ], @@ -251,7 +251,7 @@ public function toggleArchive(Request $request, StatefulGuard $guard, Project $p ]); } - $creator->toggle($project); + $creator->toggleArchive($project); return redirect()->route('dashboard')->with('success', 'Project archive state updated successfully'); } @@ -324,12 +324,21 @@ public function validationReport(Request $request, Project $project) public function publish(Request $request, Project $project, PublishProject $publisher, UpdateProject $updater) { + if (! Gate::forUser($request->user())->allows('publishProject', $project)) { + return response()->json(['message' => 'Forbidden'], 403); + } + if ($project) { $input = $request->all(); - $release_date = $input['release_date']; + $release_date = $request->get('release_date'); $enableProjectMode = $request->get('enableProjectMode'); if ($enableProjectMode) { $validation = $project->validation; + if (! $validation) { + return response()->json([ + 'errors' => 'Project validation not found. Please ensure the project is properly configured.', + ], 422); + } $validation->process(); $validation = $validation->fresh(); if ($validation['report']['project']['status']) { @@ -351,16 +360,20 @@ public function publish(Request $request, Project $project, PublishProject $publ } } else { $draft = $project->draft; - $draft->project_enabled = false; - $draft->save(); + if ($draft) { + $draft->project_enabled = false; + $draft->save(); + } $project->release_date = $request->get('release_date'); $project->status = 'queued'; $project->save(); $validation = $project->validation; - $validation->process(); - $validation = $validation->fresh(); + if ($validation) { + $validation->process(); + $validation = $validation->fresh(); + } foreach ($project->studies as $study) { $study->license_id = $project->license_id; @@ -373,9 +386,11 @@ public function publish(Request $request, Project $project, PublishProject $publ $status = true; - foreach ($validation['report']['project']['studies'] as $study) { - if (! $study['status']) { - $status = false; + if ($validation && isset($validation['report']['project']['studies'])) { + foreach ($validation['report']['project']['studies'] as $study) { + if (! $study['status']) { + $status = false; + } } } // add license check @@ -398,7 +413,7 @@ public function publish(Request $request, Project $project, PublishProject $publ public function store(Request $request, CreateNewProject $creator) { - if (! Gate::forUser($request->user())->check('createProject', $project)) { + if (! Gate::forUser($request->user())->allows('createProject', Project::class)) { throw new AuthorizationException; } diff --git a/app/Http/Controllers/RorController.php b/app/Http/Controllers/RorController.php new file mode 100644 index 00000000..74d2b613 --- /dev/null +++ b/app/Http/Controllers/RorController.php @@ -0,0 +1,63 @@ +all(), [ + 'query' => 'required|string|min:3|max:255', + ]); + + if ($validator->fails()) { + return response()->json([ + 'error' => 'Invalid search query', + 'messages' => $validator->errors(), + ], 422); + } + + $query = $request->input('query'); + + try { + $response = Http::timeout(10) + ->acceptJson() + ->get(config('ror.api_url', 'https://api.ror.org/organizations'), [ + 'query' => $query, + ]); + + if ($response->successful()) { + return response()->json($response->json()); + } + + Log::warning('ROR API request failed', [ + 'status' => $response->status(), + 'body' => $response->body(), + ]); + + return response()->json([ + 'error' => 'Failed to fetch organizations', + 'items' => [], + ], $response->status()); + } catch (\Exception $e) { + Log::error('ROR API exception', [ + 'message' => $e->getMessage(), + 'query' => $query, + ]); + + return response()->json([ + 'error' => 'An error occurred while searching for organizations', + 'items' => [], + ], 500); + } + } +} diff --git a/app/Http/Controllers/StudyController.php b/app/Http/Controllers/StudyController.php index eb538299..c77f3648 100644 --- a/app/Http/Controllers/StudyController.php +++ b/app/Http/Controllers/StudyController.php @@ -13,7 +13,6 @@ use App\Models\Sample; use App\Models\Study; use App\Models\User; -use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -65,6 +64,8 @@ public function store(Request $request, CreateNewStudy $creator) public function update(Request $request, UpdateStudy $updater, Study $study) { + Gate::authorize('updateStudy', $study); + $updater->update($study, $request->all()); $study = $study->fresh(); @@ -78,12 +79,10 @@ public function update(Request $request, UpdateStudy $updater, Study $study) public function show(Request $request, Study $study, GetLicense $getLicense) { - if (! Gate::forUser($request->user())->check('viewStudy', $study)) { - throw new AuthorizationException; - } + Gate::forUser($request->user())->authorize('viewStudy', $study); $project = $study->project; - $team = $project->nonPersonalTeam; + $team = $project?->nonPersonalTeam; $license = null; if ($study->license_id) { $license = $getLicense->getLicensebyId($study->license_id); @@ -102,12 +101,10 @@ public function protocols(Request $request, Study $study) public function datasets(Request $request, Study $study) { - if (! Gate::forUser($request->user())->check('viewStudy', $study)) { - throw new AuthorizationException; - } + Gate::forUser($request->user())->authorize('viewStudy', $study); $project = $study->project; - $team = $project->team; + $team = $project?->team; return $this->renderTabView('Datasets', $study, $team, $project, null, null, false); } @@ -117,7 +114,7 @@ public function preview2(Request $request, $obfuscationCode, Study $study, $mode switch ($model) { case 'study': $project = Project::where([['is_archived', false], ['obfuscationcode', $obfuscationCode]])->firstOrFail(); - $team = $project->nonPersonalTeam; + $team = $project?->nonPersonalTeam; $license = null; if ($study->license_id) { $license = $getLicense->getLicensebyId($study->license_id); @@ -128,7 +125,7 @@ public function preview2(Request $request, $obfuscationCode, Study $study, $mode break; case 'files': $project = Project::where([['is_archived', false], ['obfuscationcode', $obfuscationCode]])->firstOrFail(); - $team = $project->nonPersonalTeam; + $team = $project?->nonPersonalTeam; $studyFSObject = $study->fsObject; return $this->renderTabView('Files', $study, $team, $project, null, $studyFSObject, true); @@ -136,7 +133,7 @@ public function preview2(Request $request, $obfuscationCode, Study $study, $mode break; case 'datasets': $project = Project::where([['is_archived', false], ['obfuscationcode', $obfuscationCode]])->firstOrFail(); - $team = $project->nonPersonalTeam; + $team = $project?->nonPersonalTeam; return $this->renderTabView('Datasets', $study, $team, $project, null, null, true); @@ -149,9 +146,9 @@ public function renderTabView($tab, $study, $team, $project, $license, $studyFSO switch ($tab) { case 'About': return Inertia::render('Study/About', [ - 'study' => $study->load('users', 'owner', 'studyInvitations', 'tags', 'sample.molecules'), + 'study' => $study->load('users', 'owner', 'studyInvitations', 'tags', 'sample.molecules', 'studyAuthors'), 'team' => $team ? $team->load('users', 'owner') : null, - 'project' => $project ? $project->load('users', 'owner') : null, + 'project' => $project ? $project->load('users', 'owner', 'authors') : null, 'members' => $study->allUsers(), 'preview' => $preview, 'availableRoles' => array_values(Jetstream::$roles), @@ -208,13 +205,15 @@ public function renderTabView($tab, $study, $team, $project, $license, $studyFSO public function moleculeStore(Request $request, Study $study) { + Gate::forUser($request->user())->authorize('updateStudy', $study); + $sample = $study->sample; if (! $sample) { $sample = Sample::create([ 'name' => $study->name.'_sample', 'slug' => Str::slug($study->name.'_sample', '-'), 'study_id' => $study->id, - 'project_id' => $study->project->id, + 'project_id' => $study->project ? $study->project->id : null, ]); $study->sample()->save($sample); } @@ -277,6 +276,8 @@ public function nmriumVersions(Request $request, Study $study) public function nmriumInfo(Request $request, Study $study) { + Gate::forUser($request->user())->authorize('updateStudy', $study); + // $version = $request->get('version'); // $spectra = $request->get('spectra'); // $molecules = $nmriumInfo['data']['molecules']; @@ -380,12 +381,10 @@ public function moleculeDetach(Request $request, Study $study, Molecule $molecul public function files(Request $request, Study $study) { - if (! Gate::forUser($request->user())->check('viewStudy', $study)) { - throw new AuthorizationException; - } + Gate::forUser($request->user())->authorize('viewStudy', $study); $project = $study->project; - $team = $project->nonPersonalTeam; + $team = $project?->nonPersonalTeam; $studyFSObject = $study->fsObject; return $this->renderTabView('Files', $study, $team, $project, null, $studyFSObject, false); @@ -393,9 +392,7 @@ public function files(Request $request, Study $study) public function annotations(Request $request, Study $study) { - if (! Gate::forUser($request->user())->check('viewStudy', $study)) { - throw new AuthorizationException; - } + Gate::forUser($request->user())->authorize('viewStudy', $study); $studyFSObject = FileSystemObject::with('children') ->where([ @@ -439,7 +436,7 @@ public function file(Request $request, $code, Study $study, $filename) } else { if ($file) { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $path = preg_replace( '~//+~', '/', @@ -497,6 +494,8 @@ public function Notifications(Request $request, Study $study) public function settings(Request $request, Study $study) { + Gate::forUser($request->user())->authorize('viewStudy', $study); + return Inertia::render('Study/Settings', [ 'study' => $study, 'project' => $study->project, @@ -508,6 +507,8 @@ public function destroy( StatefulGuard $guard, Study $study ) { + Gate::forUser($request->user())->authorize('deleteStudy', $study); + $confirmed = app(ConfirmPassword::class)( $guard, $request->user(), @@ -545,10 +546,12 @@ public function toggleStarred(Request $request, Study $study) public function snapshot(Request $request, Study $study) { + Gate::forUser($request->user())->authorize('updateStudy', $study); + $content = $request->get('img'); if ($content) { $path = '/projects/'.$study->project->uuid.'/'.$study->slug.'.svg'; - Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->put($path, $content, 'public'); + Storage::disk(config('filesystems.default_public'))->put($path, $content, 'public'); $study->study_photo_path = $path; $study->save(); } diff --git a/app/Http/Controllers/SupportBubbleController.php b/app/Http/Controllers/SupportBubbleController.php new file mode 100644 index 00000000..a9a07a76 --- /dev/null +++ b/app/Http/Controllers/SupportBubbleController.php @@ -0,0 +1,45 @@ +input('subject'), + $request->input('message'), + $request->input('email'), + $request->input('name'), + $request->input('url'), + $request->ip(), + $request->userAgent(), + $request + )); + + return response()->view('support-bubble::success'); + + } catch (\Exception $e) { + Log::error('Support bubble submission failed', [ + 'error' => $e->getMessage(), + 'ip' => $request->ip(), + 'data' => $request->only(['email', 'subject']), + ]); + + return response()->json([ + 'success' => false, + 'message' => 'An error occurred while processing your request. Please try again.', + ], 500); + } + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index e4d527f7..116b614c 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -60,28 +60,29 @@ public function share(Request $request) 'auth.user.teamRole' => fn () => $user ? $user->teamRole($user->currentTeam) : null, 'auth.user.notifications' => fn () => $user ? $user->unreadNotifications : null, - 'twitter' => (env('TWITTER_CLIENT_ID') !== null && env('TWITTER_CLIENT_ID') !== ''), - 'github' => (env('GITHUB_CLIENT_ID') !== null && env('GITHUB_CLIENT_ID') !== ''), - 'orcid' => (env('ORCID_CLIENT_ID') !== null && env('ORCID_CLIENT_ID') !== ''), - 'nfdiaai' => (env('NFDIAAI_CLIENT_ID') !== null && env('NFDIAAI_CLIENT_ID') !== ''), + 'twitter' => (config('services.twitter.client_id') !== null && config('services.twitter.client_id') !== ''), + 'github' => (config('services.github.client_id') !== null && config('services.github.client_id') !== ''), + 'orcid' => (config('services.orcid.client_id') !== null && config('services.orcid.client_id') !== ''), + 'nfdiaai' => (config('services.regapp.client_id') !== null && config('services.regapp.client_id') !== ''), 'config.announcements' => Schema::hasTable('announcements') ? Announcement::active() : null, - 'url' => env('APP_URL'), - 'nmriumURL' => env('NMRIUM_URL'), + 'url' => config('app.url'), + 'nmriumURL' => config('external-links.nmrium_url'), 'team' => $user ? $user->currentTeam : null, - 'environment' => env('APP_ENV'), - 'MEILISEARCH_HOST' => (env('MEILISEARCH_HOST')), - 'MEILISEARCH_PUBLICKEY' => (env('MEILISEARCH_PUBLICKEY')), - 'SCOUT_PREFIX' => (env('SCOUT_PREFIX')), - 'europemcWSApi' => (env('EUROPEMC_WS_API')), - 'dataciteURL' => env('DATACITE_ENDPOINT'), - 'coolOffPeriod' => env('COOL_OFF_PERIOD'), - 'mailFromAddress' => env('MAIL_FROM_ADDRESS'), - 'orcidSearchApi' => env('ORCID_ID_SEARCH_API'), - 'orcidPersonApi' => env('ORCID_ID_PERSON_API'), - 'orcidEmploymentApi' => env('ORCID_ID_EMPLOYMENT_API'), - 'CM_API' => env('CM_API'), - 'CROSSREF_API' => env('CROSSREF_API'), - 'DATACITE_API' => env('DATACITE_API'), + 'environment' => config('app.env'), + 'MEILISEARCH_HOST' => config('scout.meilisearch.host'), + 'MEILISEARCH_PUBLICKEY' => config('scout.meilisearch.public_key'), + 'SCOUT_PREFIX' => config('scout.prefix'), + 'europemcWSApi' => config('external-links.europemc_ws_api'), + 'dataciteURL' => config('doi.datacite.endpoint'), + 'coolOffPeriod' => config('nmrxiv.cool_off_period'), + 'mailFromAddress' => config('mail.from.address'), + 'orcidSearchApi' => config('orcid.search_api'), + 'orcidPersonApi' => config('orcid.person_api'), + 'michiStandardsUrl' => config('external-links.michi_standards_url'), + 'orcidEmploymentApi' => config('orcid.employment_api'), + 'CM_API' => config('external-links.cm_api'), + 'CROSSREF_API' => config('external-links.crossref_api'), + 'DATACITE_API' => config('external-links.datacite_api'), ]); } } diff --git a/app/Http/Requests/SupportBubbleRequest.php b/app/Http/Requests/SupportBubbleRequest.php new file mode 100644 index 00000000..85a3e0e1 --- /dev/null +++ b/app/Http/Requests/SupportBubbleRequest.php @@ -0,0 +1,265 @@ +|string> + */ + public function rules(): array + { + return [ + 'email' => [ + 'required', + 'email', + 'max:255', + function ($attribute, $value, $fail) { + // Block disposable email domains + $disposableDomains = [ + '10minutemail.com', '10minutemail.net', 'guerrillamail.com', + 'mailinator.com', 'yopmail.com', 'tempmail.org', 'throwaway.email', + 'temp-mail.org', 'getnada.com', 'maildrop.cc', + ]; + + $domain = substr(strrchr($value, '@'), 1); + if (in_array(strtolower($domain), $disposableDomains)) { + $fail('Please use a permanent email address. Temporary email services are not allowed.'); + } + + // Extract username part (before @) + $username = substr($value, 0, strpos($value, '@')); + + // Enhanced gibberish detection for email usernames + if ($this->isGibberishEmail($username)) { + $fail('Please enter a valid email address. Random characters are not allowed.'); + } + + // Block suspicious patterns (original check) + if (preg_match('/^[a-z0-9]{10,20}@gmail\.com$/i', $value)) { + $fail('Please enter a valid email address. Random characters are not allowed.'); + } + }, + ], + 'name' => [ + 'nullable', + 'string', + 'max:255', + function ($attribute, $value, $fail) { + if ($value && $this->containsGibberish($value)) { + $fail('The name contains invalid characters or patterns.'); + } + }, + ], + 'subject' => [ + 'required', + 'string', + 'max:255', + 'min:3', + function ($attribute, $value, $fail) { + if ($this->containsGibberish($value)) { + $fail('The subject must contain meaningful text.'); + } + + // Check for excessive special characters + if (preg_match_all('/[^a-zA-Z0-9\s]/', $value) > strlen($value) * 0.3) { + $fail('The subject contains too many special characters.'); + } + }, + ], + 'message' => [ + 'required', + 'string', + 'max:2000', + 'min:10', + function ($attribute, $value, $fail) { + if ($this->containsGibberish($value)) { + $fail('The message must contain meaningful text.'); + } + + // Check for excessive special characters + if (preg_match_all('/[^a-zA-Z0-9\s\.\,\!\?\-]/', $value) > strlen($value) * 0.2) { + $fail('The message contains too many special characters.'); + } + + // Check for minimum word count + $wordCount = str_word_count($value); + if ($wordCount < 3) { + $fail('The message must contain at least 3 words.'); + } + }, + ], + 'url' => 'nullable|url|max:255', + 'g-recaptcha-response' => [ + function ($attribute, $value, $fail) { + if (config('services.recaptcha.site_key') && empty($value)) { + $fail('Please complete the CAPTCHA verification.'); + + return; + } + if (config('services.recaptcha.site_key') && ! empty($value)) { + $rule = new RecaptchaRule; + $rule->validate($attribute, $value, $fail); + } + }, + ], + ]; + } + + /** + * Get custom messages for validation errors. + */ + public function messages(): array + { + return [ + 'subject.min' => 'The subject must be at least 3 characters long.', + 'message.min' => 'The message must be at least 10 characters long.', + 'email.required' => 'An email address is required.', + 'email.email' => 'Please enter a valid email address format (e.g., name@domain.com).', + 'subject.required' => 'A subject is required.', + 'message.required' => 'A message is required.', + ]; + } + + /** + * Check if text contains gibberish or random character patterns + */ + protected function containsGibberish(string $text): bool + { + // Allow common words and normal text + if (strlen($text) < 15) { + return false; // Don't check short text like "Hello" + } + + // Check for random character patterns (like "αχΥηΤrvnIbuQzoGkkbYqjEr") + // High ratio of consonants without vowels + $consonantRatio = preg_match_all('/[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]/', $text); + $vowelRatio = preg_match_all('/[aeiouAEIOU]/', $text); + + if (strlen($text) > 20 && $vowelRatio == 0) { + return true; + } + + if (strlen($text) > 15 && $consonantRatio / max(strlen($text), 1) > 0.85) { + return true; + } + + // Check for mixed scripts (Latin + Greek/Cyrillic like in spam) + $hasLatin = preg_match('/[a-zA-Z]/', $text); + $hasGreek = preg_match('/[\x{0370}-\x{03FF}]/u', $text); + $hasCyrillic = preg_match('/[\x{0400}-\x{04FF}]/u', $text); + + if (($hasLatin && $hasGreek) || ($hasLatin && $hasCyrillic)) { + return true; + } + + // Check for alternating case patterns (like "αχΥηΤrvnIbu") + if (preg_match('/([a-z][A-Z]){4,}|([A-Z][a-z]){4,}/', $text)) { + return true; + } + + // Check for sequences of random characters + if (preg_match('/[a-zA-Z]{20,}/', $text) && ! preg_match('/\s/', $text)) { + return true; + } + + return false; + } + + /** + * Check if email username contains gibberish patterns + */ + protected function isGibberishEmail(string $username): bool + { + // Allow common email patterns first + if (strlen($username) < 6) { + return false; // Allow short usernames like "john", "mary" + } + + // Check for too many consecutive consonants (like "jkdkfjdks") + if (preg_match('/[bcdfghjklmnpqrstvwxyz]{4,}/i', $username)) { + return true; + } + + // Check for alternating consonant patterns without vowels + $consonantCount = preg_match_all('/[bcdfghjklmnpqrstvwxyz]/i', $username); + $vowelCount = preg_match_all('/[aeiou]/i', $username); + + // If username is mostly consonants (>80%) and longer than 6 chars, likely gibberish + if (strlen($username) > 6 && $consonantCount > 0 && ($vowelCount / max($consonantCount, 1)) < 0.25) { + return true; + } + + // Check for patterns like repeated character groups "jkjk", "dkdk" + if (preg_match('/(.{2,3})\1{2,}/', $username)) { + return true; + } + + // Check for keyboard patterns like "qwerty", "asdf", etc. + $keyboardPatterns = [ + 'qwert', 'asdf', 'zxcv', 'uiop', 'hjkl', 'bnm', + 'poiu', 'lkjh', 'mnbv', 'rewq', 'fdsa', 'vcxz', + ]; + + foreach ($keyboardPatterns as $pattern) { + if (stripos($username, $pattern) !== false) { + return true; + } + } + + // Check for completely random character sequences + // If no vowels and more than 8 characters, likely random + if (strlen($username) > 8 && $vowelCount == 0) { + return true; + } + + return false; + } + + /** + * Handle a failed validation attempt. + */ + protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator): void + { + // Log suspicious attempts for monitoring + if ($this->containsSpamIndicators()) { + Log::warning('Potential spam attempt blocked on support bubble', [ + 'ip' => $this->ip(), + 'user_agent' => $this->userAgent(), + 'data' => $this->only(['email', 'subject', 'message']), + 'errors' => $validator->errors()->toArray(), + ]); + } + + parent::failedValidation($validator); + } + + /** + * Check if the request contains spam indicators + */ + protected function containsSpamIndicators(): bool + { + $email = $this->input('email', ''); + $subject = $this->input('subject', ''); + $message = $this->input('message', ''); + + // Check for the specific patterns seen in the spam + return $this->containsGibberish($subject) || + $this->containsGibberish($message) || + preg_match('/^[a-z0-9]{10,20}@gmail\.com$/i', $email); + } +} diff --git a/app/Http/Resources/AuthorResource.php b/app/Http/Resources/AuthorResource.php index 646a82a7..a0e7da40 100644 --- a/app/Http/Resources/AuthorResource.php +++ b/app/Http/Resources/AuthorResource.php @@ -25,9 +25,13 @@ public function toArray($request): array 'affiliation' => $this->affiliation, 'contributor_type' => $this->whenPivotLoaded('author_project', function () { return $this->pivot->contributor_type; + }) ?? $this->whenPivotLoaded('author_study', function () { + return $this->pivot->contributor_type; }), 'sort_order' => $this->whenPivotLoaded('author_project', function () { return $this->pivot->sort_order; + }) ?? $this->whenPivotLoaded('author_study', function () { + return $this->pivot->sort_order; }), 'created_at' => $this->created_at?->toISOString(), 'updated_at' => $this->updated_at?->toISOString(), diff --git a/app/Http/Resources/StudyResource.php b/app/Http/Resources/StudyResource.php index 4aa2c4b4..084a93b8 100644 --- a/app/Http/Resources/StudyResource.php +++ b/app/Http/Resources/StudyResource.php @@ -8,7 +8,7 @@ class StudyResource extends JsonResource { private bool $lite = true; - private array $properties = ['sample', 'users', 'license']; + private array $properties = ['sample', 'users', 'license', 'authors']; public function lite(bool $lite, ?array $properties = []): self { @@ -33,7 +33,7 @@ public function toArray($request): array 'name' => $this->name, 'slug' => $this->slug, 'description' => $this->description, - 'molecules' => $this->sample->molecules, + 'molecules' => $this->sample ? $this->sample->molecules : [], 'team' => $this->when(! ($this->team && $this->team->personal_team), $this->team), 'photo_url' => $this->study_photo_url, 'tags' => $this->tags, @@ -112,6 +112,18 @@ function () { ), ]; }), + $this->mergeWhen(! $this->lite, function () { + return [ + $this->mergeWhen( + in_array('authors', $this->properties), + function () { + return [ + 'authors' => $this->studyAuthors ? AuthorResource::collection($this->studyAuthors) : [], + ]; + } + ), + ]; + }), ]; } } diff --git a/app/Jobs/ArchiveProject.php b/app/Jobs/ArchiveProject.php index a5a5bf54..46d8739a 100644 --- a/app/Jobs/ArchiveProject.php +++ b/app/Jobs/ArchiveProject.php @@ -63,7 +63,7 @@ public function handle(): void $fsObject = new FileSystemObject; $fsObject->type = 'directory'; $fsObject->name = $project->slug; - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $fsObject->path = $environment.'/'.$project->uuid; $fsObject->key = $project->uuid; $fsObject->status = 'present'; @@ -73,11 +73,11 @@ public function handle(): void if ($fsObject && $fsObject->status != 'missing') { $path = $fsObject->path; $s3Client = $this->storageClient(); - $bucket = config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.bucket'); + $bucket = config('filesystems.disks.'.config('filesystems.default').'.bucket'); $s3keys = []; - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); if ($fsObject->type == 'file') { - if (Storage::disk(env('FILESYSTEM_DRIVER'))->exists($path)) { + if (Storage::disk(config('filesystems.default'))->exists($path)) { array_push($s3keys, substr($fsObject->path, 1)); } } else { @@ -163,8 +163,8 @@ public function handle(): void $zip->finish(); fclose($archiveDestination); - Storage::disk(env('FILESYSTEM_DRIVER'))->setVisibility($zipFilePath, 'public'); - $url = Storage::disk(env('FILESYSTEM_DRIVER'))->url($zipFilePath); + Storage::disk(config('filesystems.default'))->setVisibility($zipFilePath, 'public'); + $url = Storage::disk(config('filesystems.default'))->url($zipFilePath); $project->download_url = $url; $project->internal_status = 'complete'; $project->save(); @@ -185,15 +185,16 @@ public function handle(): void */ protected function storageClient() { + $diskName = config('filesystems.default'); $config = [ - 'region' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.region'), + 'region' => config('filesystems.disks.'.$diskName.'.region'), 'version' => 'latest', 'use_path_style_endpoint' => true, - 'url' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), - 'endpoint' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), + 'url' => config('filesystems.disks.'.$diskName.'.endpoint'), + 'endpoint' => config('filesystems.disks.'.$diskName.'.endpoint'), 'credentials' => [ - 'key' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.key'), - 'secret' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.secret'), + 'key' => config('filesystems.disks.'.$diskName.'.key'), + 'secret' => config('filesystems.disks.'.$diskName.'.secret'), ], ]; diff --git a/app/Jobs/ArchiveStudy.php b/app/Jobs/ArchiveStudy.php index 7cb918a8..7df9cb71 100644 --- a/app/Jobs/ArchiveStudy.php +++ b/app/Jobs/ArchiveStudy.php @@ -84,7 +84,7 @@ public function handle(): void Log::info("Study {$study->id}: Using filesystem driver: {$filesystemDriver}, bucket: {$bucket}"); $s3keys = []; - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $relative_URL = $fsObject->relative_url; if ($fsObject->type == 'file') { Log::info("Study {$study->id}: Processing single file"); @@ -298,7 +298,7 @@ private function generateS3Url(string $filePath, string $filesystemDriver, strin // protected function standardizeMolecule($mol) // { - // $response = Http::post('https://api.cheminf.studio/latest/chem/standardize', $mol); + // $response = Http::post('https://api.naturalproducts.net/latest/chem/standardize', $mol); // return $response->json(); // } @@ -322,15 +322,16 @@ private function generateS3Url(string $filePath, string $filesystemDriver, strin */ protected function storageClient() { + $diskName = config('filesystems.default'); $config = [ - 'region' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.region'), + 'region' => config('filesystems.disks.'.$diskName.'.region'), 'version' => 'latest', 'use_path_style_endpoint' => true, - 'url' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), - 'endpoint' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.endpoint'), + 'url' => config('filesystems.disks.'.$diskName.'.endpoint'), + 'endpoint' => config('filesystems.disks.'.$diskName.'.endpoint'), 'credentials' => [ - 'key' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.key'), - 'secret' => config('filesystems.disks.'.env('FILESYSTEM_DRIVER').'.secret'), + 'key' => config('filesystems.disks.'.$diskName.'.key'), + 'secret' => config('filesystems.disks.'.$diskName.'.secret'), ], ]; diff --git a/app/Jobs/DataBackupJob.php b/app/Jobs/DataBackupJob.php index bf6610da..a612674a 100644 --- a/app/Jobs/DataBackupJob.php +++ b/app/Jobs/DataBackupJob.php @@ -30,9 +30,9 @@ public function handle(): void Artisan::call('backup:run --only-db'); // Define the bucket name and prefix. - $bucket = env('AWS_BUCKET'); - $prefix = env('APP_ENV').'/database'; - $disk = Storage::disk(env('FILESYSTEM_DRIVER')); + $bucket = config('filesystems.disks.ceph.bucket'); + $prefix = config('app.env').'/database'; + $disk = Storage::disk(config('filesystems.default')); // Get the contents of the bucket with the specified prefix. $contents = $disk->listContents($prefix, false); @@ -46,9 +46,9 @@ public function handle(): void // Set the visibility to public $disk->setVisibility($latestFile['path'], 'public'); - } - // Print the URL of the latest file. - echo 'Download the file from URL '.$latestFile['path']; + // Print the URL of the latest file. + echo 'Download the file from URL '.$latestFile['path']; + } } } diff --git a/app/Jobs/DeleteProjects.php b/app/Jobs/DeleteProjects.php index 18da9637..bc1fb7f7 100644 --- a/app/Jobs/DeleteProjects.php +++ b/app/Jobs/DeleteProjects.php @@ -40,9 +40,9 @@ public function handle(DeleteProject $deleteProject): void $project = $this->project; $deletedOn = $project->deleted_on; $diffInDays = null; - $coolOffPeriod = (int) env('COOL_OFF_PERIOD', '30'); + $coolOffPeriod = config('nmrxiv.cool_off_period'); if ($deletedOn) { - $diffInDays = Carbon::parse($deletedOn)->diffInDays(Carbon::now()); + $diffInDays = (int) Carbon::parse($deletedOn)->diffInDays(Carbon::now()); // Sending reminder to user 1 week and 1 day before. if ($diffInDays == ($coolOffPeriod - 7) || $diffInDays == ($coolOffPeriod - 1)) { $project->sendNotification('deletionReminder', $this->prepareSendList($project)); diff --git a/app/Jobs/ProcessDraftELNSubmission.php b/app/Jobs/ProcessDraftELNSubmission.php index 5c2b0b22..6485d932 100644 --- a/app/Jobs/ProcessDraftELNSubmission.php +++ b/app/Jobs/ProcessDraftELNSubmission.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Actions\Author\SyncProjectAuthors; use App\Actions\Draft\DraftProcessingLogger; use App\Actions\Draft\ProcessDraft; use App\Http\Controllers\FileSystemController; @@ -10,7 +11,6 @@ use App\Models\License; use App\Models\Molecule; use App\Models\Sample; -use App\Services\AuthorService; use App\Services\ChemotionRepositoryTrackerService; use App\Services\ELNMetadataServiceFactory; use App\Services\FileSystemObjectService; @@ -54,7 +54,8 @@ public function handle( try { $logger->log($draft, 'info', 'Starting ELN submission processing'); - if (strtolower($draft->eln) !== 'chemotion') { + // Check if ELN is supported + if (! in_array(strtolower($draft->eln), ['chemotion', 'nobs'])) { $logger->log($draft, 'info', "ELN not supported: {$draft->eln}"); return; @@ -364,47 +365,89 @@ private function attachMetadataToStudy($project, array $studyMetadata, DraftProc }) ->first(); + // If no direct match found, try to find studies within the sample folder + $studiesInSample = collect(); if (! $study) { - $logger->log($project->draft, 'error', 'Study not found: '.$studyName); + $logger->log($project->draft, 'info', 'Direct study not found. Looking for studies within sample folder: '.$studyName); + + // Find all studies whose name starts with the analysis folders in this sample + // The folder structure is: sample_X/analysis_Y/dataset_Z + // Studies are created with names like "dataset_Z" + $folderName = $studyMetadata['folderName'] ?? $studyName; + + // Get analysis IDs from the metadata datasets + $analysisIds = []; + if (isset($studyMetadata['chemical_substance']['datasets'])) { + foreach ($studyMetadata['chemical_substance']['datasets'] as $dataset) { + if (isset($dataset['analyses'])) { + $analysisIds[] = $dataset['analyses']; + } + if (isset($dataset['datasets']) && is_array($dataset['datasets'])) { + foreach ($dataset['datasets'] as $datasetId) { + $studiesInSample->push($project->studies()->where('name', $datasetId)->first()); + } + } + } + } - return; + $studiesInSample = $studiesInSample->filter(); + + if ($studiesInSample->isEmpty()) { + $logger->log($project->draft, 'error', 'No studies found for sample folder: '.$studyName); + + return; + } + + $logger->log($project->draft, 'info', 'Found '.$studiesInSample->count().' studies in sample folder'); + } else { + $studiesInSample = collect([$study]); } - // Get the draft to access processing logs - $draft = $project->draft; - $processingLogs = $draft ? $draft->process_logs : []; + // Process metadata for each study in the sample + foreach ($studiesInSample as $study) { + if (! $study) { + continue; + } - $study->update([ - 'name' => $studyMetadata['name'].' ('.$studyName.')', - 'external_url' => $studyMetadata['url'], - 'tracking_item_name' => $studyMetadata['tracking_item_name'], - 'processing_logs' => $processingLogs, - ]); + // Get the draft to access processing logs + $draft = $project->draft; + $processingLogs = $draft ? $draft->process_logs : []; + + $study->update([ + 'name' => $studyMetadata['name'].' ('.$study->name.')', + 'external_url' => $studyMetadata['url'], + 'tracking_item_name' => $studyMetadata['tracking_item_name'], + 'processing_logs' => $processingLogs, + ]); - // update STATUS_PROCESSED in Chemotion Repository Tracker - $trackerService = app(ChemotionRepositoryTrackerService::class); - $trackerService->updateElnSubmissionStatus( - submissionId: $study->tracking_item_name, - newStatus: ChemotionRepositoryTrackerService::STATUS_PROCESSED, - additionalMetadata: $studyMetadata, - ownerName: $study->owner->first_name.' '.$study->owner->last_name, - ownerEmail: $study->owner->email - ); + $logger->log($project->draft, 'info', 'Attaching metadata to study: '.$study->name); - $logger->log($project->draft, 'info', 'Attaching metadata to study: '.$study->name); + $this->updateStudyDescription($study, $studyMetadata, $logger); - $this->updateStudyDescription($study, $studyMetadata, $logger); + $this->attachLicenseToStudy($study, $studyMetadata, $logger); - $this->attachLicenseToStudy($study, $studyMetadata, $logger); + $this->attachKeywordsToStudy($study, $studyMetadata, $logger); - $this->attachKeywordsToStudy($study, $studyMetadata, $logger); + $this->attachAuthorsToStudy($study, $studyMetadata['authors'], $logger); - $this->attachAuthorsToStudy($study, $studyMetadata['authors'], $logger); + // $this->attachCitationsToStudy($study, $studyMetadata['citation'] ?? [], $logger); - // $this->attachCitationsToStudy($study, $studyMetadata['citation'] ?? [], $logger); + if (isset($studyMetadata['chemical_substance']['molecule'])) { + $this->attachMoleculesToStudy($study, [$studyMetadata['chemical_substance']['molecule']], $logger); + } + } - if (isset($studyMetadata['chemical_substance']['molecule'])) { - $this->attachMoleculesToStudy($study, [$studyMetadata['chemical_substance']['molecule']], $logger); + // update STATUS_PROCESSED in Chemotion Repository Tracker (only once for the first study) + if ($studiesInSample->isNotEmpty()) { + $firstStudy = $studiesInSample->first(); + $trackerService = app(ChemotionRepositoryTrackerService::class); + $trackerService->updateElnSubmissionStatus( + submissionId: $firstStudy->tracking_item_name, + newStatus: ChemotionRepositoryTrackerService::STATUS_PROCESSED, + additionalMetadata: $studyMetadata, + ownerName: $firstStudy->owner->first_name.' '.$firstStudy->owner->last_name, + ownerEmail: $firstStudy->owner->email + ); } } catch (\Exception $e) { @@ -450,8 +493,11 @@ private function attachAuthorsToStudy($study, array $authors, DraftProcessingLog } if (! empty($authorData)) { - $authorService = app(AuthorService::class); - $authorService->syncAuthors($project, $authorData); + // Sync authors to project + app(SyncProjectAuthors::class)->handle($project, $authorData); + + // Also sync authors directly to the study for independent studies + $this->syncStudyAuthors($study, $authorData, $logger); } } catch (\Exception $e) { @@ -459,6 +505,55 @@ private function attachAuthorsToStudy($study, array $authors, DraftProcessingLog } } + /** + * Sync authors to study. + */ + private function syncStudyAuthors($study, array $authorData, DraftProcessingLogger $logger): void + { + try { + $authorIds = []; + + foreach ($authorData as $index => $data) { + // Find existing author by ORCID or name + $author = null; + + if (! empty($data['orcid_id'])) { + $author = \App\Models\Author::where('orcid_id', $data['orcid_id'])->first(); + } + + if (! $author) { + $author = \App\Models\Author::where('given_name', $data['given_name']) + ->where('family_name', $data['family_name']) + ->first(); + } + + // Create if not found + if (! $author) { + $author = \App\Models\Author::create([ + 'given_name' => $data['given_name'], + 'family_name' => $data['family_name'], + 'email_id' => $data['email_id'], + 'orcid_id' => $data['orcid_id'], + 'affiliation' => $data['affiliation'], + ]); + } + + $authorIds[$author->id] = [ + 'contributor_type' => $data['contributor_type'], + 'sort_order' => $index + 1, + ]; + } + + // Sync authors to study + $study->studyAuthors()->sync($authorIds); + + $logger->log($study->project->draft, 'info', 'Synced '.count($authorIds).' authors to study: '.$study->name); + + } catch (\Exception $e) { + $logger->log($study->project->draft, 'error', 'Failed to sync authors to study: '.$e->getMessage()); + } + } + /** * Attach citations to study. */ diff --git a/app/Jobs/ProcessMetadataExtractionBagitGenerationJob.php b/app/Jobs/ProcessMetadataExtractionBagitGenerationJob.php new file mode 100644 index 00000000..d71c923e --- /dev/null +++ b/app/Jobs/ProcessMetadataExtractionBagitGenerationJob.php @@ -0,0 +1,475 @@ +tries = config('nmrxiv.spectra_parsing.job_tries', 3); + $this->timeout = config('nmrxiv.spectra_parsing.job_timeout', 600); + $this->retries = config('nmrxiv.spectra_parsing.retry_count', 3); + } + + /** + * Execute the job. + */ + public function handle(): void + { + try { + $study = Study::with('datasets')->find($this->studyId); + + if (! $study) { + throw new \Exception("Study {$this->studyId} not found"); + } + + // Mark as processing + $study->update([ + 'metadata_bagit_generation_status' => 'processing', + 'metadata_bagit_generation_logs' => array_merge((array) ($study->metadata_bagit_generation_logs ?: []), [ + 'started_at' => now()->toIso8601String(), + ]), + ]); + + Log::info("Processing metadata extraction for study {$study->id} ({$study->identifier})"); + + // Process the study with BagIt structure + $result = $this->processStudy($study); + + // Mark as completed with metadata + $study->update([ + 'metadata_bagit_generation_status' => 'completed', + 'metadata_bagit_generation_logs' => array_merge((array) ($study->metadata_bagit_generation_logs ?: []), [ + 'completed_at' => now()->toIso8601String(), + 'storage_path' => $result['location'], + 'image_count' => $result['imageCount'], + ]), + ]); + + Log::info("Successfully processed study {$study->id} ({$study->identifier}): {$result['imageCount']} images saved to {$result['location']}"); + } catch (\Exception $e) { + Log::error("Failed to process study {$this->studyId}: {$e->getMessage()}"); + + // Mark as failed with error message + $study = Study::find($this->studyId); + if ($study) { + $study->update([ + 'metadata_bagit_generation_status' => 'failed', + 'metadata_bagit_generation_logs' => array_merge((array) ($study->metadata_bagit_generation_logs ?: []), [ + 'failed_at' => now()->toIso8601String(), + 'error_message' => $e->getMessage(), + ]), + ]); + } + + // Don't rethrow - let the job complete so it doesn't retry infinitely + } + } + + /** + * Process a single study with BagIt structure. + */ + protected function processStudy(Study $study): array + { + // Remove NMRXIV: prefix if present (e.g., "NMRXIV:S1295" -> "S1295") + $studyIdentifier = str_replace('NMRXIV:', '', $study->identifier); + $disk = Storage::disk(config('nmrxiv.spectra_parsing.storage_disk', 'local')); + $basePath = config('nmrxiv.spectra_parsing.storage_path', 'spectra_parse'); + $baseDir = "{$basePath}/{$studyIdentifier}"; + $dataDir = "{$baseDir}/data"; + $zipPath = null; + + try { + // Step 1: Download ZIP file + Log::info("Step 1/7: Downloading ZIP file for study {$study->id}"); + $zipPath = $this->downloadWithRetry($study->download_url, $this->retries); + + // Step 2: Extract ZIP to data directory + Log::info('Step 2/7: Extracting ZIP archive...'); + $studyName = $this->extractZip($zipPath, $disk->path($dataDir)); + + // Step 3: Call NMRKit API + Log::info('Step 3/7: Calling NMRKit API...'); + $jsonData = $this->callNMRKitAPI($study->download_url, $this->retries); + + // Step 4: Fetch Bio-Schema + Log::info('Step 4/7: Fetching bio-schema...'); + $bioSchema = null; + try { + $bioSchema = $this->fetchBioSchema($studyIdentifier, $this->retries); + } catch (\Exception $e) { + Log::warning("Bio-schema fetch failed: {$e->getMessage()}. Continuing without bio-schema..."); + } + + // Step 5: Create nmrxiv-meta structure + Log::info('Step 5/7: Creating nmrxiv-meta structure...'); + $metaDir = "{$dataDir}/{$studyName}/nmrxiv-meta"; + $imagesDir = "{$metaDir}/images"; + + if (! $disk->exists($metaDir)) { + $disk->makeDirectory($metaDir, 0755, true); + } + + // Clean up old images directory to prevent duplicates from previous runs + if ($disk->exists($imagesDir)) { + // Delete all PNG files in the images directory + $oldImages = $disk->files($imagesDir); + foreach ($oldImages as $oldImage) { + $disk->delete($oldImage); + } + Log::info('Cleaned up '.count($oldImages).' old image files'); + } else { + $disk->makeDirectory($imagesDir, 0755, true); + } + + // Clean up spectra data + if (isset($jsonData['data']['spectra']) && is_array($jsonData['data']['spectra'])) { + foreach ($jsonData['data']['spectra'] as &$spectra) { + unset($spectra['data']); + unset($spectra['meta']); + unset($spectra['originalData']); + unset($spectra['originalInfo']); + } + unset($spectra); + } + + // Extract and save images as PNG files + $imageCount = 0; + if (isset($jsonData['images']) && is_array($jsonData['images'])) { + foreach ($jsonData['images'] as $imageData) { + if (isset($imageData['id']) && isset($imageData['image'])) { + $imageId = $imageData['id']; + $base64Data = $imageData['image']; + + // Save PNG file + $pngPath = "{$imagesDir}/{$imageId}.png"; + $this->savePNGFromBase64($base64Data, $disk->path($pngPath)); + $imageCount++; + } + } + } + + // Save S{identifier}.nmrium (full API response with base64 images intact) + $nmriumPath = "{$metaDir}/{$studyIdentifier}.nmrium"; + $formattedJson = json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + $disk->put($nmriumPath, $formattedJson); + + // Save bio-schema.json + if ($bioSchema !== null) { + $bioSchemaPath = "{$metaDir}/bio-schema.json"; + $bioSchemaJson = json_encode($bioSchema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + $disk->put($bioSchemaPath, $bioSchemaJson); + } + + // Step 6: Generate BagIt manifests + Log::info('Step 6/7: Generating BagIt manifests...'); + $this->generateBagItManifests($disk->path($baseDir)); + + return [ + 'imageCount' => $imageCount, + 'location' => $disk->path($baseDir), + ]; + } finally { + // Step 7: Cleanup temporary files (always runs, even on exception) + if ($zipPath && file_exists($zipPath)) { + Log::info('Step 7/7: Cleaning up temporary ZIP file...'); + @unlink($zipPath); + } + } + } + + /** + * Download file with retry logic. + */ + protected function downloadWithRetry(string $url, int $retries): string + { + $attempt = 0; + $lastException = null; + + while ($attempt < $retries) { + try { + $attempt++; + Log::debug("Download attempt {$attempt}/{$retries}..."); + + $tempPath = storage_path('app/temp_'.uniqid().'.zip'); + $timeout = config('nmrxiv.spectra_parsing.download_timeout', 300); + $response = Http::timeout($timeout)->get($url); + + if (! $response->successful()) { + throw new \Exception("Download failed with status {$response->status()}"); + } + + file_put_contents($tempPath, $response->body()); + + return $tempPath; + } catch (\Exception $e) { + $lastException = $e; + if ($attempt < $retries) { + Log::warning("Download failed: {$e->getMessage()}. Retrying..."); + sleep(2); + } + } + } + + throw new \Exception("Download failed after {$retries} attempts: ".$lastException->getMessage()); + } + + /** + * Extract ZIP file and return the study name. + */ + protected function extractZip(string $zipPath, string $extractTo): string + { + $zip = new ZipArchive; + + if ($zip->open($zipPath) !== true) { + throw new \Exception("Failed to open ZIP file: {$zipPath}"); + } + + // Get the root folder name from first entry + $studyName = null; + if ($zip->numFiles > 0) { + $firstEntry = $zip->getNameIndex(0); + $parts = explode('/', $firstEntry); + $studyName = $parts[0]; + } + + if (! $studyName) { + throw new \Exception('Could not determine study name from ZIP'); + } + + // Extract all files + $zip->extractTo($extractTo); + $zip->close(); + + return $studyName; + } + + /** + * Call NMRKit API with retry logic. + */ + protected function callNMRKitAPI(string $url, int $retries): array + { + $attempt = 0; + $lastException = null; + + while ($attempt < $retries) { + try { + $attempt++; + Log::debug("NMRKit API attempt {$attempt}/{$retries}..."); + + $timeout = config('nmrxiv.spectra_parsing.api_timeout', 300); + $apiUrl = config('nmrxiv.spectra_parsing.nmrkit_api_url'); + + $response = Http::timeout($timeout) + ->post($apiUrl, [ + 'url' => $url, + 'capture_snapshot' => true, + 'auto_processing' => true, + 'auto_detection' => true, + ]); + + if (! $response->successful()) { + throw new \Exception("API request failed with status {$response->status()}: {$response->body()}"); + } + + return $response->json(); + } catch (\Exception $e) { + $lastException = $e; + if ($attempt < $retries) { + Log::warning("API call failed: {$e->getMessage()}. Retrying..."); + sleep(2); + } + } + } + + throw new \Exception("API call failed after {$retries} attempts: ".$lastException->getMessage()); + } + + /** + * Fetch bio-schema from nmrxiv.org API with retry logic. + */ + protected function fetchBioSchema(string $studyIdentifier, int $retries): array + { + $attempt = 0; + $lastException = null; + $baseUrl = config('nmrxiv.spectra_parsing.bioschema_api_url'); + $url = "{$baseUrl}/{$studyIdentifier}"; + + while ($attempt < $retries) { + try { + $attempt++; + Log::debug("Bio-schema attempt {$attempt}/{$retries}..."); + + $response = Http::timeout(60)->get($url); + + if (! $response->successful()) { + throw new \Exception("Bio-schema request failed with status {$response->status()}"); + } + + return $response->json(); + } catch (\Exception $e) { + $lastException = $e; + if ($attempt < $retries) { + Log::warning("Bio-schema fetch failed: {$e->getMessage()}. Retrying..."); + sleep(2); + } + } + } + + throw new \Exception("Bio-schema fetch failed after {$retries} attempts: ".$lastException->getMessage()); + } + + /** + * Save PNG image from base64 data. + */ + protected function savePNGFromBase64(string $base64Data, string $outputPath): void + { + // Remove data:image/png;base64, prefix if present + $base64Data = preg_replace('/^data:image\/[a-z]+;base64,/', '', $base64Data); + + $imageData = base64_decode($base64Data); + + if ($imageData === false) { + throw new \Exception('Failed to decode base64 image data'); + } + + file_put_contents($outputPath, $imageData); + } + + /** + * Generate BagIt manifests using whikloj/BagItTools library. + */ + protected function generateBagItManifests(string $bagPath): void + { + try { + // Create bag using BagItTools library + $bag = Bag::create($bagPath); + + // Update bag with checksums + $bag->update(); + + // Package the bag (this generates manifests) + $bag->package($bagPath); + + Log::debug('Used BagItTools library for manifest generation'); + } catch (\Exception $e) { + Log::warning("BagIt library failed: {$e->getMessage()}. Falling back to manual generation..."); + + // Fallback: Generate manually + $this->generateBagItManually($bagPath); + } + } + + /** + * Manually generate BagIt manifests. + */ + protected function generateBagItManually(string $bagPath): void + { + // Create bagit.txt + $bagitContent = "BagIt-Version: 1.0\nTag-File-Character-Encoding: UTF-8\n"; + file_put_contents($bagPath.'/bagit.txt', $bagitContent); + + // Create manifest-sha256.txt + $manifestLines = []; + $dataPath = $bagPath.'/data'; + + if (is_dir($dataPath)) { + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dataPath), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $file) { + if ($file->isFile()) { + $relativePath = str_replace($bagPath.'/', '', $file->getPathname()); + $hash = hash_file('sha256', $file->getPathname()); + $manifestLines[] = "{$hash} {$relativePath}"; + } + } + } + + file_put_contents($bagPath.'/manifest-sha256.txt', implode("\n", $manifestLines)."\n"); + + // Create bag-info.txt + $bagInfoContent = 'Payload-Oxum: '.$this->calculatePayloadOxum($dataPath)."\n"; + $bagInfoContent .= 'Bagging-Date: '.date('Y-m-d')."\n"; + $bagInfoContent .= "Bag-Software-Agent: Laravel-Queue-ProcessStudySpectraJob/1.0\n"; + file_put_contents($bagPath.'/bag-info.txt', $bagInfoContent); + + // Create tagmanifest-sha256.txt + $tagManifestLines = []; + foreach (['bagit.txt', 'bag-info.txt', 'manifest-sha256.txt'] as $tagFile) { + $tagFilePath = $bagPath.'/'.$tagFile; + if (file_exists($tagFilePath)) { + $hash = hash_file('sha256', $tagFilePath); + $tagManifestLines[] = "{$hash} {$tagFile}"; + } + } + file_put_contents($bagPath.'/tagmanifest-sha256.txt', implode("\n", $tagManifestLines)."\n"); + + Log::debug('Manual BagIt generation complete'); + } + + /** + * Calculate Payload-Oxum (total bytes.total files). + */ + protected function calculatePayloadOxum(string $dataPath): string + { + $totalBytes = 0; + $totalFiles = 0; + + if (is_dir($dataPath)) { + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dataPath), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $file) { + if ($file->isFile()) { + $totalBytes += $file->getSize(); + $totalFiles++; + } + } + } + + return "{$totalBytes}.{$totalFiles}"; + } +} diff --git a/app/Jobs/ProcessProject.php b/app/Jobs/ProcessProject.php index 960f6da1..c8f9f52e 100644 --- a/app/Jobs/ProcessProject.php +++ b/app/Jobs/ProcessProject.php @@ -58,7 +58,7 @@ public function handle(AssignIdentifier $assigner, UpdateDOI $updater, PublishPr $draft = $project->draft; if ($draft) { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $projectPath = preg_replace( '~//+~', @@ -128,7 +128,7 @@ public function moveFolder($fsObject, $draft, $path) $path, $fsObjectChild->path ); - Storage::disk(env('FILESYSTEM_DRIVER'))->move($fsObjectChild->path, $newPath); + Storage::disk(config('filesystems.default'))->move($fsObjectChild->path, $newPath); $fsObjectChild->path = $newPath; $fsObjectChild->save(); } else { diff --git a/app/Jobs/ProcessProjectSpectra.php b/app/Jobs/ProcessProjectSpectra.php deleted file mode 100644 index 7c73d8b8..00000000 --- a/app/Jobs/ProcessProjectSpectra.php +++ /dev/null @@ -1,303 +0,0 @@ -projectId); - - if (! $project) { - Log::error("Project not found: {$this->projectId}"); - - return; - } - - try { - Log::info("Starting spectra processing for project: {$project->identifier}"); - - $this->processProjectStudies($project); - - Log::info("Successfully completed spectra processing for project: {$project->identifier}"); - - } catch (Exception $e) { - Log::error("Failed to process spectra for project {$project->identifier}: ".$e->getMessage()); - throw $e; - } - } - - /** - * Process all studies in the project. - */ - private function processProjectStudies(Project $project): void - { - $studies = $project->studies; - - foreach ($studies as $study) { - Log::info("Processing study: {$study->identifier}"); - - try { - // Process study-level NMRium data - $this->processStudySpectra($study); - - // Process dataset-level NMRium data - $this->processStudyDatasets($study->fresh()); - - } catch (Exception $e) { - Log::error("Failed to process study {$study->identifier}: ".$e->getMessage()); - - // Continue with other studies instead of failing the entire job - continue; - } - } - } - - /** - * Process spectra for a single study. - */ - private function processStudySpectra($study): void - { - if ($study->has_nmrium) { - return; // Already processed - } - - DB::transaction(function () use ($study) { - $downloadUrl = $study->download_url; - - if (! $downloadUrl) { - Log::warning("No download URL found for study: {$study->identifier}"); - - return; - } - - $nmriumData = $this->processSpectra($downloadUrl); - - if (! $nmriumData || ! isset($nmriumData['data'])) { - Log::warning("No valid spectra data returned for study: {$study->identifier}"); - - return; - } - - $parsedSpectra = $nmriumData['data']; - - // Clean up spectra data - foreach ($parsedSpectra['spectra'] as &$spectra) { - unset($spectra['data']); - unset($spectra['meta']); - unset($spectra['originalData']); - unset($spectra['originalInfo']); - } - - $version = $parsedSpectra['version'] ?? null; - unset($parsedSpectra['version']); - - $nmriumJSON = [ - 'data' => $parsedSpectra, - 'version' => $version, - ]; - - // Create or update NMRium record - $nmrium = $study->nmrium; - - if ($nmrium) { - $nmrium->nmrium_info = json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE); - $nmrium->save(); - } else { - $nmrium = NMRium::create([ - 'nmrium_info' => json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE), - ]); - $study->nmrium()->save($nmrium); - } - - $study->has_nmrium = true; - $study->save(); - - Log::info("Successfully processed study spectra: {$study->identifier}"); - }); - } - - /** - * Process datasets for a study. - */ - private function processStudyDatasets($study): void - { - if (! $study->has_nmrium) { - Log::warning("Study {$study->identifier} has no NMRium data, skipping datasets"); - - return; - } - - $nmriumInfo = json_decode($study->nmrium->nmrium_info, true); - - if (! isset($nmriumInfo['data']['spectra']) || count($nmriumInfo['data']['spectra']) == 0) { - Log::warning("Study {$study->identifier} has no spectra info, skipping datasets"); - - return; - } - - foreach ($study->datasets as $dataset) { - if ($dataset->has_nmrium) { - continue; // Already processed - } - - try { - $this->processDatasetSpectra($dataset, $study, $nmriumInfo); - } catch (Exception $e) { - Log::error("Failed to process dataset {$dataset->identifier}: ".$e->getMessage()); - - continue; - } - } - } - - /** - * Process spectra for a single dataset. - */ - private function processDatasetSpectra($dataset, $study, $nmriumInfo): void - { - $nmriumJSON = $nmriumInfo; - $fsObject = $dataset->fsObject; - $studyFSObject = $study->fsObject; - $datasetFSObject = $dataset->fsObject; - $draft = $study->draft; - - // Determine path based on ELN type - if ($draft && $draft->eln == 'chemotion') { - $path = '/'.$studyFSObject->name.'/'.$datasetFSObject->parent->name.'/'.$datasetFSObject->name; - } else { - $path = '/'.$studyFSObject->name.'/'.$datasetFSObject->name; - } - - $fType = $studyFSObject->type; - $matchingSpectra = []; - $types = []; - - // Find matching spectra for this dataset - foreach ($nmriumInfo['data']['spectra'] as $spectra) { - if ($this->spectraMatchesDataset($spectra, $path, $fType)) { - $matchingSpectra[] = $spectra; - $types[] = $this->extractSpectraType($spectra); - } - } - - if (count($matchingSpectra) > 0) { - // Create dataset-specific NMRium data - unset($nmriumJSON['data']['spectra']); - $nmriumJSON['data']['spectra'] = $matchingSpectra; - - // Create or update NMRium record for dataset - $nmrium = $dataset->nmrium; - - if ($nmrium) { - $nmrium->nmrium_info = json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE); - $nmrium->save(); - } else { - $nmrium = NMRium::create([ - 'nmrium_info' => json_encode($nmriumJSON, JSON_UNESCAPED_UNICODE), - ]); - $dataset->nmrium()->save($nmrium); - } - - $dataset->has_nmrium = true; - - Log::info("Successfully processed dataset spectra: {$dataset->identifier}"); - } else { - Log::info("No matching spectra found for dataset: {$dataset->identifier}"); - } - - // Always update dataset type if unique (regardless of whether spectra were found) - $uniqueTypes = array_unique(array_filter($types)); - if (count($uniqueTypes) == 1) { - $dataset->type = $uniqueTypes[0]; - } - - $dataset->save(); - } - - /** - * Check if spectra matches the dataset path. - */ - private function spectraMatchesDataset($spectra, string $path, string $fType): bool - { - $files = $spectra['sourceSelector']['files'] ?? []; - - if (! $files) { - return false; - } - - foreach ($files as $file) { - $searchPath = $fType == 'file' ? $path : $path.'/'; - if (str_contains($file, $searchPath)) { - return true; - } - } - - return false; - } - - /** - * Extract spectra type from spectra info. - */ - private function extractSpectraType($spectra): ?string - { - if (! isset($spectra['info']['experiment'])) { - return null; - } - - $experiment = $spectra['info']['experiment']; - $nucleus = $spectra['info']['nucleus'] ?? null; - - if (is_array($nucleus)) { - $nucleus = implode('-', $nucleus); - } - - return $nucleus ? "{$experiment} - {$nucleus}" : $experiment; - } - - /** - * Process spectra using external service. - */ - private function processSpectra(string $url): ?array - { - try { - $encodedUrl = urlencode($url); - - $response = Http::timeout(300)->post('https://nodejs.nmrxiv.org/spectra-parser', [ - 'urls' => [$encodedUrl], - 'snapshot' => false, - ]); - - if (! $response->successful()) { - Log::error('Spectra processing service returned error: '.$response->status()); - - return null; - } - - return $response->json(); - - } catch (Exception $e) { - Log::error("Failed to process spectra from URL {$url}: ".$e->getMessage()); - - return null; - } - } -} diff --git a/app/Jobs/ProcessSubmission.php b/app/Jobs/ProcessSubmission.php index 160b5f89..fb7e7c1e 100644 --- a/app/Jobs/ProcessSubmission.php +++ b/app/Jobs/ProcessSubmission.php @@ -58,7 +58,7 @@ public function handle(AssignIdentifier $assigner, UpdateDOI $updater, PublishPr if ($project) { if ($draft) { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); $projectPath = preg_replace( '~//+~', @@ -99,14 +99,17 @@ public function handle(AssignIdentifier $assigner, UpdateDOI $updater, PublishPr $project->draft_id = null; - $project->status = 'complete'; + $release_date = Carbon::parse($project->release_date); + if ($release_date->isFuture()) { + $project->status = 'embargo'; + } else { + $project->status = 'published'; + } $project->save(); $assigner->assign($project->fresh()); - $release_date = Carbon::parse($project->release_date); - if ($release_date->isPast()) { $projectPublisher->publish($project); } @@ -120,7 +123,7 @@ public function handle(AssignIdentifier $assigner, UpdateDOI $updater, PublishPr if ($project) { $_studies = $project->studies; if ($draft) { - $environment = env('APP_ENV', 'local'); + $environment = config('app.env', 'local'); foreach ($_studies as $study) { // $study->users()->sync($project->user()->getDictionary()); @@ -199,7 +202,7 @@ public function moveFolder($fsObject, $draft, $path) $path, $fsObjectChild->path ); - Storage::disk(env('FILESYSTEM_DRIVER'))->move($fsObjectChild->path, $newPath); + Storage::disk(config('filesystems.default'))->move($fsObjectChild->path, $newPath); $fsObjectChild->path = $newPath; $fsObjectChild->save(); } else { diff --git a/app/Jobs/VerifyFileIntegrityJob.php b/app/Jobs/VerifyFileIntegrityJob.php index 33e3369f..191df21d 100644 --- a/app/Jobs/VerifyFileIntegrityJob.php +++ b/app/Jobs/VerifyFileIntegrityJob.php @@ -74,34 +74,32 @@ public function handle(FileIntegrityService $integrityService): void // Handle files without existing checksums (calculate initial checksums) if (! $this->fileSystemObject->getPrimaryChecksum()) { - Log::info('No existing checksum found, calculating initial checksums', [ + Log::info('No existing checksum found, skipping verification', [ 'file_id' => $this->fileSystemObject->id, 'file_name' => $this->fileSystemObject->name, ]); try { - // Calculate and store initial checksums - $fileIntegrityService = app(FileIntegrityService::class); - $result = $fileIntegrityService->verifyFileIntegrity($this->fileSystemObject, true); + // Attempt to verify file integrity (will mark as failed if no checksum exists) + $verificationSuccess = $integrityService->verifyFileIntegrity($this->fileSystemObject); - if ($result['success']) { - Log::info('Initial checksums calculated and stored successfully', [ + if ($verificationSuccess) { + Log::info('File integrity verification completed successfully', [ 'file_id' => $this->fileSystemObject->id, 'file_name' => $this->fileSystemObject->name, - 'checksums' => $result['checksums'] ?? null, ]); } else { - Log::error('Failed to calculate initial checksums', [ + Log::warning('File integrity verification failed - no checksum available', [ 'file_id' => $this->fileSystemObject->id, 'file_name' => $this->fileSystemObject->name, - 'error' => $result['error'] ?? 'Unknown error', + 'error' => $this->fileSystemObject->integrity_error, ]); } return; } catch (\Exception $e) { - Log::error('Exception while calculating initial checksums', [ + Log::error('Exception while verifying file without checksum', [ 'file_id' => $this->fileSystemObject->id, 'file_name' => $this->fileSystemObject->name, 'error' => $e->getMessage(), diff --git a/app/Mail/DraftProcessedNotifyAdmins.php b/app/Mail/DraftProcessedNotifyAdmins.php index 1dadab2f..43c353da 100644 --- a/app/Mail/DraftProcessedNotifyAdmins.php +++ b/app/Mail/DraftProcessedNotifyAdmins.php @@ -34,13 +34,14 @@ public function __construct($project, $studies) public function build() { $releasedToday = false; - $releaseDate = Carbon::parse($this->project->release_date); - - if ($releaseDate->isToday()) { - $releasedToday = true; - } if ($this->project) { + $releaseDate = Carbon::parse($this->project->release_date); + + if ($releaseDate->isToday()) { + $releasedToday = true; + } + return $this->markdown('vendor.mail.project-published-notify-admins', [ 'url' => url(config('app.url').'/dashboard/projects/'.$this->project->id), 'projectName' => $this->project->name, diff --git a/app/Mail/ProjectDeletion.php b/app/Mail/ProjectDeletion.php index 8d1547b7..2b02e53f 100644 --- a/app/Mail/ProjectDeletion.php +++ b/app/Mail/ProjectDeletion.php @@ -30,10 +30,10 @@ public function __construct($project) */ public function build() { - $coolOffPeriod = (int) env('COOL_OFF_PERIOD', '30'); + $coolOffPeriod = config('nmrxiv.cool_off_period'); return $this->markdown('vendor.mail.project-deletion', [ - 'url' => url(config('app.url').'/dashboard/projects/'.$this->project->id.'/settings'), + 'url' => url('/dashboard/projects/'.$this->project->id.'/settings'), 'projectName' => $this->project->name, 'deletedOn' => explode(' ', $this->project->deleted_on)[0], 'dueDate' => explode(' ', Carbon::parse($this->project->deleted_on)->addDays($coolOffPeriod))[0], diff --git a/app/Mail/ProjectDeletionReminder.php b/app/Mail/ProjectDeletionReminder.php index b726b406..30c406e5 100644 --- a/app/Mail/ProjectDeletionReminder.php +++ b/app/Mail/ProjectDeletionReminder.php @@ -30,10 +30,10 @@ public function __construct($project) */ public function build() { - $coolOffPeriod = (int) env('COOL_OFF_PERIOD', '30'); + $coolOffPeriod = config('nmrxiv.cool_off_period'); return $this->markdown('vendor.mail.project-deletion-reminder', [ - 'url' => url(config('app.url').'/dashboard/projects/'.$this->project->id), + 'url' => url('/dashboard/projects/'.$this->project->id), 'projectName' => $this->project->name, 'deletedOn' => explode(' ', $this->project->deleted_on)[0], 'dueDate' => explode(' ', Carbon::parse($this->project->deleted_on)->addDays($coolOffPeriod))[0], diff --git a/app/Mail/StudyPublish.php b/app/Mail/StudyPublish.php index caa270da..96da6fdf 100644 --- a/app/Mail/StudyPublish.php +++ b/app/Mail/StudyPublish.php @@ -31,7 +31,7 @@ public function __construct($studies) public function build() { $releaseToday = false; - $releaseDate = Carbon::parse($this->studies[0]['release_date']); + $releaseDate = Carbon::parse($this->studies[0]->release_date); if ($releaseDate->isToday()) { $releaseToday = true; diff --git a/app/Models/Author.php b/app/Models/Author.php index 111409ed..e59a9577 100644 --- a/app/Models/Author.php +++ b/app/Models/Author.php @@ -22,10 +22,16 @@ class Author extends Model 'family_name', 'email_id', 'affiliation', + 'ror_id', ]; public function projects(): BelongsToMany { return $this->belongsToMany(Project::class); } + + public function studies(): BelongsToMany + { + return $this->belongsToMany(Study::class, 'author_study'); + } } diff --git a/app/Models/Dataset.php b/app/Models/Dataset.php index 30b19d9f..3c529251 100644 --- a/app/Models/Dataset.php +++ b/app/Models/Dataset.php @@ -52,6 +52,17 @@ class Dataset extends Model implements Auditable 'dataset_photo_url', ]; + /** + * The attributes that should be cast. + */ + protected function casts(): array + { + return [ + 'starred' => 'boolean', + 'is_public' => 'boolean', + ]; + } + /** * Get the dataset identifier */ @@ -70,19 +81,19 @@ protected function identifier(): Attribute public function getDatasetPhotoUrlAttribute() { return $this->dataset_photo_path - ? Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->url($this->dataset_photo_path) + ? Storage::disk(config('filesystems.default_public'))->url($this->dataset_photo_path) : ''; } protected function getPublicUrlAttribute() { // return env('APP_URL', null).'/datasets/'.urlencode($this->slug); - return env('APP_URL', null).'/dataset/D'.$this->getRawOriginal('identifier'); + return config('app.url').'/dataset/D'.$this->getRawOriginal('identifier'); } protected function getPrivateUrlAttribute() { - return env('APP_URL', null).'/datasets/'.urlencode($this->url); + return config('app.url').'/datasets/'.urlencode($this->url); } public function study(): BelongsTo diff --git a/app/Models/HasDOI.php b/app/Models/HasDOI.php index 8ea41df8..d151fb13 100644 --- a/app/Models/HasDOI.php +++ b/app/Models/HasDOI.php @@ -6,7 +6,7 @@ trait HasDOI { public function generateDOI($doiService) { - $doi_host = env('DOI_HOST', null); + $doi_host = config('doi.host'); if (! is_null($doi_host)) { $identifier = $this->getIdentifier($this, 'identifier'); @@ -71,7 +71,7 @@ public function generateDOI($doiService) */ public function updateDOIMetadata($doiService) { - $doi_host = env('DOI_HOST', null); + $doi_host = config('doi.host'); if (! is_null($doi_host)) { $doi = $this->doi; diff --git a/app/Models/Molecule.php b/app/Models/Molecule.php index 84390bb0..26481c56 100644 --- a/app/Models/Molecule.php +++ b/app/Models/Molecule.php @@ -46,7 +46,7 @@ protected function identifier(): Attribute protected function getPublicUrlAttribute() { - return env('APP_URL', null).'/compound/M'.$this->getRawOriginal('identifier'); + return config('app.url').'/compound/M'.$this->getRawOriginal('identifier'); } public function samples(): BelongsToMany diff --git a/app/Models/NMRium.php b/app/Models/NMRium.php index b402ccb2..9e10f4cd 100644 --- a/app/Models/NMRium.php +++ b/app/Models/NMRium.php @@ -19,6 +19,8 @@ class NMRium extends Model protected $fillable = [ 'nmrium_info', + 'nmriumable_id', + 'nmriumable_type', 'dataset_id', ]; diff --git a/app/Models/Project.php b/app/Models/Project.php index 8cad91ce..79783516 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -42,6 +42,9 @@ class Project extends Model implements Auditable 'starred', 'location', 'is_public', + 'is_deleted', + 'is_archived', + 'status', 'obfuscationcode', 'description', 'type', @@ -79,7 +82,7 @@ class Project extends Model implements Auditable public function getProjectPhotoUrlAttribute() { return $this->project_photo_path - ? Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->url($this->project_photo_path) + ? Storage::disk(config('filesystems.default_public'))->url($this->project_photo_path) : ''; } @@ -264,12 +267,12 @@ public function removeUser($user) protected function getPublicUrlAttribute() { // return env('APP_URL', null).'/projects/'.$this->owner->username.'/'.urlencode($this->slug); - return env('APP_URL', null).'/project/P'.$this->getRawOriginal('identifier'); + return config('app.url').'/project/P'.$this->getRawOriginal('identifier'); } protected function getPrivateUrlAttribute() { - return env('APP_URL', null).'/projects/'.urlencode($this->url); + return config('app.url').'/projects/'.urlencode($this->url); } /** diff --git a/app/Models/ProjectInvitation.php b/app/Models/ProjectInvitation.php index 024c8096..d0ec49e9 100644 --- a/app/Models/ProjectInvitation.php +++ b/app/Models/ProjectInvitation.php @@ -2,17 +2,21 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class ProjectInvitation extends Model { + use HasFactory; + /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ + 'project_id', 'email', 'role', 'message', diff --git a/app/Models/Study.php b/app/Models/Study.php index 8de2b3fb..e3af0657 100644 --- a/app/Models/Study.php +++ b/app/Models/Study.php @@ -38,6 +38,7 @@ class Study extends Model implements Auditable 'starred', 'location', 'is_public', + 'is_archived', 'obfuscationcode', 'description', 'type', @@ -60,6 +61,11 @@ class Study extends Model implements Auditable 'external_url', 'processing_logs', 'tracking_item_name', + 'doi', + 'identifier', + 'validation_id', + 'metadata_bagit_generation_status', + 'metadata_bagit_generation_logs', ]; /** @@ -68,10 +74,13 @@ class Study extends Model implements Auditable protected function casts(): array { return [ - 'authors' => 'array', 'citations' => 'array', 'molecules' => 'array', 'processing_logs' => 'array', + 'metadata_bagit_generation_logs' => 'array', + 'starred' => 'boolean', + 'is_public' => 'boolean', + 'is_archived' => 'boolean', ]; } @@ -131,7 +140,7 @@ protected function identifier(): Attribute public function getStudyPhotoUrlAttribute() { return $this->study_photo_path - ? Storage::disk(env('FILESYSTEM_DRIVER_PUBLIC'))->url($this->study_photo_path) + ? Storage::disk(config('filesystems.default_public'))->url($this->study_photo_path) : ''; } @@ -173,12 +182,12 @@ public function project(): BelongsTo protected function getPublicUrlAttribute() { // return env('APP_URL', null).'/projects/'.$this->owner->username.'/'.urlencode($this->project->slug).'?tab=study&id='.$this->slug; - return env('APP_URL', null).'/sample/S'.$this->getRawOriginal('identifier'); + return config('app.url').'/sample/S'.$this->getRawOriginal('identifier'); } protected function getPrivateUrlAttribute() { - return env('APP_URL', null).'/studies/'.urlencode($this->url); + return config('app.url').'/studies/'.urlencode($this->url); } public function draft(): BelongsTo @@ -311,9 +320,7 @@ public function removeUser($user) */ public function shouldBeSearchable() { - if ($this->is_public && ! $this->is_archived) { - return true; - } + return $this->is_public && ! $this->is_archived; } /** @@ -335,6 +342,25 @@ public function license(): BelongsTo return $this->belongsTo(License::class, 'license_id'); } + /** + * Get all of the authors that belong to the study. + */ + public function studyAuthors(): BelongsToMany + { + return $this->belongsToMany(Author::class) + ->withPivot('contributor_type', 'sort_order')->orderBy('sort_order', 'asc'); + } + + /** + * Accessor for authors attribute (alias for studyAuthors relationship). + */ + protected function authors(): Attribute + { + return Attribute::make( + get: fn () => $this->studyAuthors, + ); + } + public function scopeFilter($query, array $filters) { $query->when($filters['search'] ?? null, function ($query, $search) { diff --git a/app/Models/StudyInvitation.php b/app/Models/StudyInvitation.php index b2882e40..24508434 100644 --- a/app/Models/StudyInvitation.php +++ b/app/Models/StudyInvitation.php @@ -2,17 +2,21 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class StudyInvitation extends Model { + use HasFactory; + /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ + 'study_id', 'email', 'role', 'message', diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php index 90294d42..4c78cff4 100644 --- a/app/Models/TeamInvitation.php +++ b/app/Models/TeamInvitation.php @@ -2,18 +2,22 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Laravel\Jetstream\Jetstream; use Laravel\Jetstream\TeamInvitation as JetstreamTeamInvitation; class TeamInvitation extends JetstreamTeamInvitation { + use HasFactory; + /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ + 'team_id', 'email', 'role', 'invited_by', diff --git a/app/Models/User.php b/app/Models/User.php index 466e0763..0ebf90e6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -31,11 +31,11 @@ class User extends Authenticatable implements MustVerifyEmail /** * The attributes that are mass assignable. - * + /** * @var array */ protected $fillable = [ - 'name', 'first_name', 'last_name', 'username', 'email', 'password', 'onboarded', 'orcid_id', 'affiliation', + 'name', 'first_name', 'last_name', 'username', 'email', 'password', 'onboarded', 'orcid_id', 'affiliation', 'ror_id', ]; /** diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php index 4abc9afd..b27908aa 100644 --- a/app/Policies/ProjectPolicy.php +++ b/app/Policies/ProjectPolicy.php @@ -72,6 +72,20 @@ public function updateProject(User $user, Project $project) return $user->canUpdateProject($project); } + /** + * Determine whether the user can publish the model. + * + * @return mixed + */ + public function publishProject(User $user, Project $project) + { + if ($project->is_public || $project->is_archived || $project->is_deleted || $project->is_published) { + return false; + } + + return $user->canUpdateProject($project); + } + /** * Determine whether the user can delete the model. * diff --git a/app/Policies/StudyPolicy.php b/app/Policies/StudyPolicy.php index 079c70b8..87da593c 100644 --- a/app/Policies/StudyPolicy.php +++ b/app/Policies/StudyPolicy.php @@ -35,12 +35,16 @@ public function viewAny(User $user): bool * * @return mixed */ - public function viewStudy(User $user, Study $study) + public function viewStudy(?User $user, Study $study) { - if (is_null($user) && $study->is_public) { + if ($study->is_public) { return true; } + if (is_null($user)) { + return false; + } + return $user->belongsToStudy($study); } diff --git a/app/Providers/CASServiceProvider.php b/app/Providers/CASServiceProvider.php new file mode 100644 index 00000000..572cae57 --- /dev/null +++ b/app/Providers/CASServiceProvider.php @@ -0,0 +1,40 @@ +app->bind(CASService::class, function ($app) { + $provider = config('services.cas.provider'); + if ($provider === 'CAS_CommonChemistry') { + return new CommonChemistry; + } + throw new \Exception('Invalid CAS provider: '.$provider); + }); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index b2b03e02..8ed78205 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -73,5 +73,23 @@ protected function configureRateLimiting(): void RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); + + // Support bubble rate limiting to prevent spam + RateLimiter::for('support-bubble', function (Request $request) { + // Skip rate limiting in testing environment + if (app()->environment('testing')) { + return Limit::none(); + } + + return [ + // Allow 5 submissions per minute per IP + Limit::perMinute(1)->by($request->ip()), + // Allow 20 submissions per hour per IP + Limit::perHour(5)->by($request->ip()), + // Allow 50 submissions per day per IP + Limit::perDay(20)->by($request->ip()), + + ]; + }); } } diff --git a/app/Services/AuthorService.php b/app/Services/AuthorService.php deleted file mode 100644 index e9c70740..00000000 --- a/app/Services/AuthorService.php +++ /dev/null @@ -1,145 +0,0 @@ -load('authors'); - - $processedAuthors = []; - - foreach ($authors as $authorData) { - $this->validateAuthorData($authorData); - - $familyName = $authorData['family_name']; - $givenName = $authorData['given_name']; - - if (! is_null($familyName) && ! is_null($givenName)) { - $author = $this->findOrCreateAuthor($project, $authorData, $familyName, $givenName); - $author->contributor_type = $authorData['contributor_type'] ?? 'Researcher'; - $processedAuthors[] = $author; - } - } - - // Use database transaction for bulk operations - DB::transaction(function () use ($project, $processedAuthors): void { - $this->updater->attachAuthor($project, $processedAuthors); - }); - - // Reload the relationship to get fresh pivot data - $project->load('authors'); - - return $processedAuthors; - } - - /** - * Remove author from project. - */ - public function removeAuthorFromProject(Project $project, int $authorId): void - { - DB::transaction(function () use ($project, $authorId): void { - $this->updater->detachAuthor($project, $authorId); - }); - } - - /** - * Update contributor type for an author in a project. - */ - public function updateContributorType(Project $project, int $authorId, string $role): bool - { - $contributorTypes = Config::get('doi.'.Config::get('doi.default').'.contributor_types'); - - if (! in_array($role, $contributorTypes)) { - return false; - } - - $this->updater->updateContributorType($project, $authorId, $role); - - return true; - } - - /** - * Validate author data against validation rules. - * - * - * @throws \Illuminate\Validation\ValidationException - */ - private function validateAuthorData(array $authorData): void - { - Validator::make($authorData, [ - 'given_name' => ['required', 'string', 'max:255'], - 'family_name' => ['required', 'string', 'max:255'], - 'title' => ['nullable', 'string', 'max:100'], - 'email_id' => ['nullable', 'email', 'max:320'], - 'orcid_id' => ['nullable', 'string', 'max:19'], - 'affiliation' => ['nullable', 'string', 'max:500'], - 'contributor_type' => ['nullable', 'string', 'max:50'], - ])->validate(); - } - - /** - * Find existing author for project or create new one. - */ - private function findOrCreateAuthor(Project $project, array $authorData, string $familyName, string $givenName): Author - { - $existingAuthor = Author::query() - ->whereHas('projects', function ($query) use ($project): void { - $query->where('projects.id', $project->id); - }) - ->where('family_name', $familyName) - ->where('given_name', $givenName) - ->first(); - - if ($existingAuthor) { - $existingAuthor->update($this->prepareAuthorAttributes($authorData, $familyName, $givenName)); - - return $existingAuthor; - } - - return Author::create($this->prepareAuthorAttributes($authorData, $familyName, $givenName)); - } - - /** - * Prepare author attributes for create or update operations. - */ - private function prepareAuthorAttributes(array $authorData, string $familyName, string $givenName): array - { - return [ - 'title' => $authorData['title'] ?? null, - 'given_name' => $givenName, - 'family_name' => $familyName, - 'orcid_id' => $authorData['orcid_id'] ?? null, - 'email_id' => $authorData['email_id'] ?? null, - 'affiliation' => $authorData['affiliation'] ?? null, - ]; - } -} diff --git a/app/Services/CAS/CASService.php b/app/Services/CAS/CASService.php new file mode 100644 index 00000000..3eced8a6 --- /dev/null +++ b/app/Services/CAS/CASService.php @@ -0,0 +1,16 @@ + Config::get('services.cas.base_url'), + 'api_token' => Config::get('services.cas.api_token'), + ]; + } + + /** + * Fetch detailed information for a given CAS registry number + */ + public function getCASDetails(string $casNumber): array + { + try { + $config = $this->getApiConfig(); + + $response = Http::timeout(self::REQUEST_TIMEOUT) + ->withHeaders([ + 'X-API-KEY' => $config['api_token'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ]) + ->get("{$config['base_url']}/detail", [ + 'cas_rn' => $casNumber, + ]); + + if ($response->successful()) { + $data = $response->json(); + + // Ensure we have valid array data + if (is_array($data)) { + return $data; + } + + throw new \Exception('Invalid response format from CAS API'); + } + + throw new \Exception('Unable to retrieve CAS details. Please verify the CAS number and try again.'); + } catch (\Exception $e) { + throw new \Exception('Unable to retrieve CAS details. Please verify the CAS number and try again.'); + } + } + + /** + * Search for CAS registry number using SMILES molecular structure notation + */ + public function searchCASBySmiles(string $smiles): ?string + { + try { + $config = $this->getApiConfig(); + + $response = Http::timeout(self::REQUEST_TIMEOUT) + ->withHeaders([ + 'X-API-KEY' => $config['api_token'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ]) + ->get("{$config['base_url']}/search", [ + 'q' => $smiles, + ]); + + if ($response->successful()) { + $data = $response->json(); + + if (isset($data['count']) && $data['count'] > 0 && isset($data['results'][0]['rn'])) { + return $data['results'][0]['rn']; + } + } + + return null; + + } catch (\Exception $e) { + return null; + } + } +} diff --git a/app/Services/ELN/NOBSMetadataService.php b/app/Services/ELN/NOBSMetadataService.php new file mode 100644 index 00000000..ef7fe573 --- /dev/null +++ b/app/Services/ELN/NOBSMetadataService.php @@ -0,0 +1,460 @@ +id], + ])->first(); + + if (! $publicationMetadataFile) { + Log::warning('Publication metadata file not found', [ + 'draft_id' => $draft->id, + ]); + + return null; + } + + $publicationMetadataContents = $this->fileIntegrityService->downloadFileFromStorage($publicationMetadataFile); + + if ($publicationMetadataContents === null) { + Log::warning('Could not download publication metadata file', [ + 'file_id' => $publicationMetadataFile->id, + 'path' => $publicationMetadataFile->path, + 'draft_id' => $draft->id, + ]); + + return null; + } + + $decodedMetadata = json_decode($publicationMetadataContents, true); + + if (! $decodedMetadata || ! is_array($decodedMetadata)) { + Log::warning('Invalid publication metadata JSON', [ + 'draft_id' => $draft->id, + 'file_id' => $publicationMetadataFile->id, + ]); + + return null; + } + + return $decodedMetadata; + } + + /** + * Extract project information (root level). + */ + public function extractProject(array $metadata): array + { + return [ + 'id' => $metadata['@id'] ?? null, + 'name' => $metadata['name'] ?? null, + 'description' => $metadata['description'] ?? null, + 'tracking_item_name' => $metadata['trackingItemName'] ?? null, + 'url' => $metadata['url'] ?? null, + 'license' => $metadata['license'] ?? null, + 'date_created' => $metadata['dateCreated'] ?? null, + 'date_modified' => $metadata['dateModified'] ?? null, + 'date_published' => $metadata['datePublished'] ?? null, + 'keywords' => $this->extractKeywords($metadata), + 'authors' => $this->extractAuthors($metadata), + 'publisher' => [ + 'name' => $metadata['publisher']['name'] ?? null, + 'logo' => $metadata['publisher']['logo'] ?? null, + 'url' => $metadata['publisher']['url'] ?? null, + ], + 'citation' => $metadata['citation'] ?? [], + ]; + } + + /** + * Extract studies from hasPart (can be single object or array). + */ + public function extractStudies(array $metadata): array + { + $studies = []; + $hasPart = $metadata['hasPart'] ?? null; + + if (! $hasPart) { + return $studies; + } + + // Handle both single object and array cases + $studyItems = isset($hasPart['@type']) ? [$hasPart] : $hasPart; + + if (! is_array($studyItems)) { + return $studies; + } + + foreach ($studyItems as $item) { + if (isset($item['@type']) && $item['@type'] === 'Study') { + $studies[] = [ + 'id' => $item['@id'] ?? null, + 'name' => $item['name'] ?? null, + 'tracking_item_name' => $item['trackingItemName'] ?? null, + 'description' => $item['description'] ?? null, + 'url' => $item['url'] ?? null, + 'license' => $item['license'] ?? null, + 'date_created' => $item['dateCreated'] ?? null, + 'date_modified' => $item['dateModified'] ?? null, + 'date_published' => $item['datePublished'] ?? null, + 'keywords' => $this->extractKeywords($item), + 'authors' => $this->extractAuthors($item), + 'citation' => $item['citation'] ?? [], + 'chemical_substance' => $this->extractChemicalSubstance($item), + ]; + } + } + + return $studies; + } + + /** + * Extract chemical substance from study's "about" section. + */ + private function extractChemicalSubstance(array $study): ?array + { + if (! isset($study['about']) || $study['about']['@type'] !== 'ChemicalSubstance') { + return null; + } + + $substance = $study['about']; + + return [ + 'id' => $substance['@id'] ?? null, + 'name' => $substance['name'] ?? null, + 'description' => $substance['description'] ?? null, + 'url' => $substance['url'] ?? null, + 'study_domain' => $substance['studyDomain'] ?? null, + 'study_subject' => $substance['studySubject'] ?? null, + 'molecule' => $this->extractMolecule($substance), + 'datasets' => $this->extractDatasets($substance), + ]; + } + + /** + * Extract molecule information from hasBioChemEntityPart. + */ + private function extractMolecule(array $substance): ?array + { + if (! isset($substance['hasBioChemEntityPart'])) { + return null; + } + + $molecule = $substance['hasBioChemEntityPart']; + + return [ + 'id' => $molecule['@id'] ?? null, + 'name' => $molecule['name'] ?? null, + 'molecular_formula' => $molecule['molecularFormula'] ?? null, + 'molecular_weight' => $molecule['molecularWeight']['value'] ?? null, + 'molecular_weight_unit' => $molecule['molecularWeight']['unitCode'] ?? null, + 'inchi' => $molecule['inChI'] ?? null, + 'inchi_key' => $molecule['inChIKey'] ?? null, + 'smiles' => $molecule['smiles'] ?? null, + 'iupac_name' => $molecule['iupacName'] ?? null, + ]; + } + + /** + * Extract datasets from chemical substance's hasPart (can be single object or array). + */ + private function extractDatasets(array $substance): array + { + $datasets = []; + $hasPart = $substance['hasPart'] ?? null; + + if (! $hasPart) { + return $datasets; + } + + // Handle both single object and array cases + $datasetItems = isset($hasPart['@type']) ? [$hasPart] : $hasPart; + + if (! is_array($datasetItems)) { + return $datasets; + } + + foreach ($datasetItems as $item) { + if (isset($item['@type']) && $item['@type'] === 'Dataset') { + $datasets[] = [ + 'id' => $item['@id'] ?? null, + 'name' => $item['name'] ?? null, + 'description' => $item['description'] ?? null, + 'url' => $item['url'] ?? null, + 'license' => $item['license'] ?? null, + 'date_created' => $item['dateCreated'] ?? null, + 'date_modified' => $item['dateModified'] ?? null, + 'date_published' => $item['datePublished'] ?? null, + 'analyses' => $item['analyses'] ?? null, + 'datasets' => $item['datasets'] ?? [], + 'measurement_technique' => $this->extractMeasurementTechnique($item), + 'variable_measured' => $this->extractVariableMeasured($item), + 'is_accessible_for_free' => $item['isAccessibleForFree'] ?? null, + ]; + } + } + + return $datasets; + } + + /** + * Extract analyses information. + */ + public function extractAnalyses(array $metadata): array + { + $analyses = []; + $studies = $this->extractStudies($metadata); + + foreach ($studies as $study) { + if (isset($study['chemical_substance']['datasets'])) { + foreach ($study['chemical_substance']['datasets'] as $dataset) { + if (isset($dataset['analyses'])) { + $analyses[] = [ + 'study_id' => $study['id'], + 'study_name' => $study['name'], + 'dataset_id' => $dataset['id'], + 'dataset_name' => $dataset['name'], + 'analysis_id' => $dataset['analyses'], + 'datasets' => $dataset['datasets'] ?? [], + 'measurement_technique' => $dataset['measurement_technique'], + 'variable_measured' => $dataset['variable_measured'], + 'external_url' => $dataset['url'], + ]; + } + } + } + } + + return $analyses; + } + + /** + * Extract molecules from all studies. + */ + public function extractMolecules(array $metadata): array + { + $molecules = []; + $studies = $this->extractStudies($metadata); + + foreach ($studies as $study) { + if (isset($study['chemical_substance']['molecule'])) { + $molecule = $study['chemical_substance']['molecule']; + $molecule['study_name'] = $study['name']; + $molecule['substance_name'] = $study['chemical_substance']['name']; + $molecules[] = $molecule; + } + } + + return $molecules; + } + + /** + * Extract all metadata in a structured format. + */ + public function extractAllMetadata(array $metadata): array + { + return [ + 'eln_type' => $this->getELNType(), + 'project' => $this->extractProject($metadata), + 'studies' => $this->extractStudies($metadata), + 'analyses' => $this->extractAnalyses($metadata), + 'molecules' => $this->extractMolecules($metadata), + ]; + } + + /** + * Extract authors from metadata. + */ + private function extractAuthors(array $metadata): array + { + $authors = []; + if (isset($metadata['author']) && is_array($metadata['author'])) { + foreach ($metadata['author'] as $author) { + $authors[] = [ + 'name' => $author['name'] ?? null, + 'given_name' => $author['givenName'] ?? null, + 'family_name' => $author['familyName'] ?? null, + 'identifier' => $author['identifier'] ?? null, + 'affiliation' => $author['affiliation']['name'] ?? null, + ]; + } + } + + return $authors; + } + + /** + * Extract keywords information. + */ + private function extractKeywords(array $metadata): array + { + $keywords = []; + + if (isset($metadata['keywords']) && is_array($metadata['keywords'])) { + foreach ($metadata['keywords'] as $keyword) { + $keywords[] = [ + 'name' => $keyword['name'] ?? null, + 'id' => $keyword['@id'] ?? null, + 'alternate_name' => $keyword['alternateName'] ?? null, + 'defined_term_set' => [ + 'name' => $keyword['inDefinedTermSet']['name'] ?? null, + 'id' => $keyword['inDefinedTermSet']['@id'] ?? null, + ], + ]; + } + } + + return $keywords; + } + + /** + * Extract measurement technique from dataset. + */ + private function extractMeasurementTechnique(array $dataset): ?array + { + if (! isset($dataset['measurementTechnique'])) { + return null; + } + + $technique = $dataset['measurementTechnique']; + + return [ + 'name' => $technique['name'] ?? null, + 'term_code' => $technique['termCode'] ?? null, + 'id' => $technique['@id'] ?? null, + 'alternate_names' => $technique['alternateName'] ?? [], + 'url' => $technique['url'] ?? null, + 'defined_term_set' => [ + 'name' => $technique['inDefinedTermSet']['name'] ?? null, + 'id' => $technique['inDefinedTermSet']['@id'] ?? null, + ], + ]; + } + + /** + * Extract variable measurements from dataset. + */ + private function extractVariableMeasured(array $dataset): array + { + $measurements = []; + + if (isset($dataset['variableMeasured']) && is_array($dataset['variableMeasured'])) { + foreach ($dataset['variableMeasured'] as $variable) { + $measurements[] = [ + 'name' => $variable['name'] ?? null, + 'property_id' => $variable['propertyID'] ?? null, + 'value' => $variable['value'] ?? null, + ]; + } + } + + return $measurements; + } + + /** + * Validate the NOBS metadata structure. + */ + public function validateMetadata(array $metadata): bool + { + // Basic validation for NOBS JSON-LD structure + $requiredFields = ['@context', '@type', 'name', 'hasPart']; + + foreach ($requiredFields as $field) { + if (! isset($metadata[$field])) { + Log::warning("Missing required field in NOBS metadata: {$field}"); + + return false; + } + } + + // Validate that it's a Study type + if ($metadata['@type'] !== 'Study') { + Log::warning("Invalid @type in NOBS metadata. Expected 'Study', got: ".($metadata['@type'] ?? 'null')); + + return false; + } + + // Validate schema.org context + if ($metadata['@context'] !== 'https://schema.org') { + Log::warning("Invalid @context in NOBS metadata. Expected 'https://schema.org'"); + + return false; + } + + return true; + } + + /** + * Get the ELN type this extractor handles. + */ + public function getELNType(): string + { + return 'nobs'; + } + + /** + * Extract analyses information from draft (fetches metadata internally). + */ + public function extractAnalysesFromDraft(Draft $draft): array + { + $metadata = $this->fetchPublicationMetadata($draft); + + if (! $metadata) { + return []; + } + + return $this->extractAnalyses($metadata); + } + + /** + * Validate metadata from draft (fetches metadata internally). + */ + public function validateMetadataFromDraft(Draft $draft): bool + { + $metadata = $this->fetchPublicationMetadata($draft); + + if (! $metadata) { + return false; + } + + return $this->validateMetadata($metadata); + } + + /** + * Extract all metadata from draft (fetches metadata internally). + */ + public function extractAllMetadataFromDraft(Draft $draft): ?array + { + $metadata = $this->fetchPublicationMetadata($draft); + + if (! $metadata) { + return null; + } + + return $this->extractAllMetadata($metadata); + } +} diff --git a/app/Services/ELNMetadataServiceFactory.php b/app/Services/ELNMetadataServiceFactory.php index 2ddc99d6..2991e083 100644 --- a/app/Services/ELNMetadataServiceFactory.php +++ b/app/Services/ELNMetadataServiceFactory.php @@ -4,6 +4,7 @@ use App\Services\ELN\ChemotionMetadataService; use App\Services\ELN\ELNMetadataExtractorInterface; +use App\Services\ELN\NOBSMetadataService; use InvalidArgumentException; /** @@ -21,6 +22,7 @@ public static function create(string $elnType): ELNMetadataExtractorInterface { return match (strtolower($elnType)) { 'chemotion' => new ChemotionMetadataService(app(FileIntegrityService::class)), + 'nobs' => new NOBSMetadataService(app(FileIntegrityService::class)), default => throw new InvalidArgumentException("Unsupported ELN type: {$elnType}") }; } @@ -32,7 +34,7 @@ public static function getSupportedELNTypes(): array { return [ 'chemotion', - // Add more ELN types here as they are implemented + 'nobs', ]; } diff --git a/app/Services/Socialite/NFDIAAI/Provider.php b/app/Services/Socialite/NFDIAAI/Provider.php index c6e15bb5..f0c86507 100644 --- a/app/Services/Socialite/NFDIAAI/Provider.php +++ b/app/Services/Socialite/NFDIAAI/Provider.php @@ -14,7 +14,25 @@ class Provider extends AbstractProvider protected function getAuthUrl($state) { - return $this->buildAuthUrlFromBase('https://regapp.nfdi-aai.de/oidc/realms/nfdi/protocol/openid-connect/auth', $state); + $url = $this->buildAuthUrlFromBase('https://regapp.nfdi-aai.de/oidc/realms/nfdi/protocol/openid-connect/auth', $state); + + // Ensure client_id is in the URL + if (! str_contains($url, 'client_id=')) { + $separator = str_contains($url, '?') ? '&' : '?'; + $url .= $separator.'client_id='.urlencode($this->clientId); + } + + return $url; + } + + protected function getCodeFields($state = null): array + { + $fields = parent::getCodeFields($state); + + // Ensure client_id is always included + $fields['client_id'] = $this->clientId; + + return $fields; } protected function getTokenUrl() diff --git a/app/Support/Csp/Policies/NmrxivPolicy.php b/app/Support/Csp/Policies/NmrxivPolicy.php new file mode 100644 index 00000000..7e602008 --- /dev/null +++ b/app/Support/Csp/Policies/NmrxivPolicy.php @@ -0,0 +1,135 @@ +add(Directive::BASE, Keyword::SELF) + ->add(Directive::DEFAULT, Keyword::SELF) + ->add(Directive::FORM_ACTION, Keyword::SELF) + ->add(Directive::OBJECT, Keyword::NONE); + + // Basic asset sources + $policy + ->add(Directive::SCRIPT, Keyword::SELF) + ->add(Directive::STYLE, Keyword::SELF) + ->add(Directive::FONT, 'data:') + ->add(Directive::CONNECT, Keyword::SELF); + + // Third-party services + $policy + ->add(Directive::STYLE, 'https://fonts.bunny.net') + ->add(Directive::SCRIPT, 'https://matomo.nfdi4chem.de') + ->add(Directive::CONNECT, 'https://matomo.nfdi4chem.de', 'https://fonts.bunny.net'); + + // Add nmrXiv-specific external sources + $this->addNmrxivSources($policy); + + // Unified rules for all environments + $this->addUnifiedRules($policy); + } + + private function addUnifiedRules(Policy $policy): void + { + // Allow unsafe-inline and unsafe-eval for maximum compatibility + // This is acceptable when you control all inline scripts and have other security layers + $policy + ->add(Directive::SCRIPT, Keyword::UNSAFE_INLINE) + ->add(Directive::SCRIPT, Keyword::UNSAFE_EVAL) + ->add(Directive::STYLE, Keyword::UNSAFE_INLINE); + + // Development server support (for local development with Vite) + $policy + ->add(Directive::SCRIPT, self::LOCAL_HOSTS) + ->add(Directive::STYLE, self::LOCAL_HOSTS) + ->add(Directive::CONNECT, array_merge(self::LOCAL_WS_HOSTS, self::LOCAL_HOSTS)); + } + + /** + * Add nmrXiv-specific external sources. + * For runtime-configurable sources, use config/csp.php with CSP_ADDITIONAL_* env variables. + */ + private function addNmrxivSources(Policy $policy): void + { + // Image sources + $policy + ->add(Directive::IMG, Keyword::SELF) + ->add(Directive::IMG, 'data:') + ->add(Directive::IMG, 'blob:') + ->add(Directive::IMG, 'https://s3.uni-jena.de') + ->add(Directive::IMG, 'https://orcid.org') + ->add(Directive::IMG, 'https://ui-avatars.com') + ->add(Directive::IMG, 'https://www.nfdi4chem.de') + ->add(Directive::IMG, 'https://www.nmrium.org') + ->add(Directive::IMG, 'https://nmriumdev.nmrxiv.org') + ->add(Directive::IMG, 'https://upload.wikimedia.org') + ->add(Directive::IMG, 'https://pbs.twimg.com') + ->add(Directive::IMG, 'https://api.cheminf.studio') + ->add(Directive::IMG, 'https://api.naturalproducts.net') + ->add(Directive::IMG, 'https://dev.api.naturalproducts.net') + ->add(Directive::IMG, 'https://placehold.co'); + + // Connection sources + $policy + ->add(Directive::CONNECT, config('external-links.datacite_api')) + ->add(Directive::CONNECT, config('external-links.crossref_api')) + ->add(Directive::CONNECT, config('doi.datacite.endpoint')) + ->add(Directive::CONNECT, config('external-links.nmrkit_url')) + ->add(Directive::CONNECT, config('services.pubchem.base_url')) + ->add(Directive::CONNECT, config('services.cas.base_url')) + ->add(Directive::CONNECT, config('services.chemistry_standardize.url')) + ->add(Directive::CONNECT, config('external-links.europemc_ws_api')) + ->add(Directive::CONNECT, config('services.chemotion_tracker.base_url')) + ->add(Directive::CONNECT, config('external-links.cm_api')) + ->add(Directive::CONNECT, config('filesystems.disks.ceph.endpoint')) + ->add(Directive::CONNECT, 'https://nmrium.nmrxiv.org') + ->add(Directive::CONNECT, 'https://nmriumdev.nmrxiv.org') + ->add(Directive::CONNECT, 'https://api.cheminf.studio') + ->add(Directive::CONNECT, 'https://api.naturalproducts.net') + ->add(Directive::CONNECT, 'https://dev.api.naturalproducts.net'); + + // Font sources + $policy + ->add(Directive::FONT, 'https://fonts.googleapis.com') + ->add(Directive::FONT, 'https://fonts.gstatic.com') + ->add(Directive::FONT, 'https://fonts.bunny.net'); + + // Frame sources + $policy + ->add(Directive::FRAME, Keyword::SELF) + ->add(Directive::FRAME, 'https://api.cheminf.studio') + ->add(Directive::FRAME, 'https://api.naturalproducts.net') + ->add(Directive::FRAME, 'https://dev.api.naturalproducts.net') + ->add(Directive::FRAME, 'https://nmrium.nmrxiv.org') + ->add(Directive::FRAME, 'https://nmriumdev.nmrxiv.org'); + } +} diff --git a/boost.json b/boost.json new file mode 100644 index 00000000..adddf227 --- /dev/null +++ b/boost.json @@ -0,0 +1,11 @@ +{ + "agents": [ + "cursor" + ], + "editors": [ + "cursor", + "vscode" + ], + "guidelines": [], + "sail": true +} diff --git a/bootstrap/app.php b/bootstrap/app.php index a2f4fda8..2e90a86c 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -34,9 +34,13 @@ \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class, \App\Http\Middleware\HandleInertiaRequests::class, \App\Http\Middleware\XFrameOptions::class, + \Spatie\Csp\AddCspHeaders::class, ]); - $middleware->throttleApi(); + // Disable API throttling in testing environment to allow test suite to run + if (env('APP_ENV') !== 'testing') { + $middleware->throttleApi(); + } $middleware->replace(\Illuminate\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 98112431..3783965e 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -2,6 +2,7 @@ return [ App\Providers\AppServiceProvider::class, + App\Providers\CASServiceProvider::class, App\Providers\CephStorageServiceProvider::class, App\Providers\DOIServiceProvider::class, App\Providers\EventServiceProvider::class, diff --git a/composer.json b/composer.json index 2dddb0e8..9345bd8a 100644 --- a/composer.json +++ b/composer.json @@ -8,15 +8,14 @@ ], "license": "MIT", "require": { - "php": "^8.2", - "aws/aws-sdk-php": "^3.208", - "darkaonline/l5-swagger": "^8.5", + "php": "^8.4", + "aws/aws-sdk-php": "^3.368", + "darkaonline/l5-swagger": "^9.0", "doctrine/dbal": "^3.5", "guzzlehttp/guzzle": "^7.8", "http-interop/http-factory-guzzle": "^1.2", "inertiajs/inertia-laravel": "^2.0", "lab404/laravel-impersonate": "^1.7", - "larabug/larabug": "^3.3", "laravel/framework": "^12.1", "laravel/horizon": "^5.30", "laravel/jetstream": "^5.3", @@ -33,17 +32,18 @@ "owen-it/laravel-auditing": "^14.0", "phpunit/php-code-coverage": "^11.0.9", "predis/predis": "^2.2", - "socialiteproviders/orcid": "^5.1", "socialiteproviders/manager": "^4.7", "spatie/laravel-backup": "^9.2", "spatie/laravel-cookie-consent": "^3.3", + "spatie/laravel-csp": "^3.18", "spatie/laravel-permission": "^6.12", "spatie/laravel-query-builder": "^6.3", "spatie/laravel-support-bubble": "^1.8", "spatie/laravel-tags": "^4.9", "spatie/laravel-welcome-notification": "^2.4", "spatie/schema-org": "^3.11", - "tightenco/ziggy": "^2.5" + "tightenco/ziggy": "^2.5", + "whikloj/bagittools": "^6.0" }, "require-dev": { "brianium/paratest": "^7.8", @@ -102,9 +102,6 @@ "allow-plugins": { "php-http/discovery": true }, - "platform": { - "php": "8.3.28" - }, "audit": { "abandoned": "ignore" } diff --git a/composer.lock b/composer.lock index ded10ad6..cb134d7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8348270f2f9171c11e79dcf9d7b1a7b7", + "content-hash": "9ba55c5bd2dcd41688398ab7e30420a2", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.365.0", + "version": "3.369.29", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "ce4b9a5fe8bad81caf40a3cca8069eba750103b1" + "reference": "068195b2980cf5cf4ade2515850d461186db3310" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ce4b9a5fe8bad81caf40a3cca8069eba750103b1", - "reference": "ce4b9a5fe8bad81caf40a3cca8069eba750103b1", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/068195b2980cf5cf4ade2515850d461186db3310", + "reference": "068195b2980cf5cf4ade2515850d461186db3310", "shasum": "" }, "require": { @@ -84,7 +84,8 @@ "guzzlehttp/psr7": "^2.4.5", "mtdowling/jmespath.php": "^2.8.0", "php": ">=8.1", - "psr/http-message": "^1.0 || ^2.0" + "psr/http-message": "^1.0 || ^2.0", + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -95,13 +96,11 @@ "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", - "ext-pcntl": "*", "ext-sockets": "*", - "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "phpunit/phpunit": "^9.6", "psr/cache": "^2.0 || ^3.0", "psr/simple-cache": "^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", - "symfony/filesystem": "^v6.4.0 || ^v7.1.0", "yoast/phpunit-polyfills": "^2.0" }, "suggest": { @@ -109,6 +108,7 @@ "doctrine/cache": "To use the DoctrineCacheAdapter", "ext-curl": "To send requests using cURL", "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-pcntl": "To use client-side monitoring", "ext-sockets": "To use client-side monitoring" }, "type": "library", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.365.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.29" }, - "time": "2025-12-02T16:06:36+00:00" + "time": "2026-02-06T19:08:50+00:00" }, { "name": "bacon/bacon-qr-code", @@ -214,16 +214,16 @@ }, { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.7", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", "shasum": "" }, "require": { @@ -262,7 +262,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.7" }, "funding": [ { @@ -270,7 +270,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-02-07T10:57:35+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -343,32 +343,33 @@ }, { "name": "darkaonline/l5-swagger", - "version": "8.6.5", + "version": "9.0.1", "source": { "type": "git", "url": "https://github.com/DarkaOnLine/L5-Swagger.git", - "reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85" + "reference": "2c26427f8c41db8e72232415e7287313e6b6a2e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85", - "reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85", + "url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/2c26427f8c41db8e72232415e7287313e6b6a2e2", + "reference": "2c26427f8c41db8e72232415e7287313e6b6a2e2", "shasum": "" }, "require": { "doctrine/annotations": "^1.0 || ^2.0", "ext-json": "*", - "laravel/framework": "^11.0 || ^10.0 || ^9.0 || >=8.40.0 || ^7.0", - "php": "^7.2 || ^8.0", - "swagger-api/swagger-ui": "^3.0 || >=4.1.3", + "laravel/framework": "^12.0 || ^11.0", + "php": "^8.2", + "swagger-api/swagger-ui": ">=5.18.3", "symfony/yaml": "^5.0 || ^6.0 || ^7.0", - "zircote/swagger-php": "^3.2.0 || ^4.0.0" + "zircote/swagger-php": "^5.0.0" }, "require-dev": { "mockery/mockery": "1.*", - "orchestra/testbench": "^9.0 || ^8.0 || 7.* || ^6.15 || 5.*", + "orchestra/testbench": "^10.0 || ^9.0 || ^8.0 || 7.* || ^6.15 || 5.*", "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "^11.0 || ^10.0 || ^9.5" + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { @@ -411,7 +412,7 @@ ], "support": { "issues": "https://github.com/DarkaOnLine/L5-Swagger/issues", - "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/8.6.5" + "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/9.0.1" }, "funding": [ { @@ -419,7 +420,7 @@ "type": "github" } ], - "time": "2025-02-06T14:54:32+00:00" + "time": "2025-02-28T06:25:02+00:00" }, { "name": "dasprid/enum", @@ -739,29 +740,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -781,22 +782,22 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/event-manager", - "version": "2.0.1", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf", "shasum": "" }, "require": { @@ -806,10 +807,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -858,7 +859,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + "source": "https://github.com/doctrine/event-manager/tree/2.1.1" }, "funding": [ { @@ -874,7 +875,7 @@ "type": "tidelift" } ], - "time": "2024-05-22T20:47:39+00:00" + "time": "2026-01-29T07:11:08+00:00" }, { "name": "doctrine/inflector", @@ -1176,16 +1177,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65", + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65", "shasum": "" }, "require": { @@ -1233,9 +1234,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.2" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2025-12-16T22:17:28+00:00" }, { "name": "fruitcake/php-cors", @@ -1310,24 +1311,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -1356,7 +1357,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -1368,7 +1369,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1783,16 +1784,16 @@ }, { "name": "http-interop/http-factory-guzzle", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/http-interop/http-factory-guzzle.git", - "reference": "8f06e92b95405216b237521cc64c804dd44c4a81" + "reference": "c2c859ceb05c3f42e710b60555f4c35b6a4a3995" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81", - "reference": "8f06e92b95405216b237521cc64c804dd44c4a81", + "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/c2c859ceb05c3f42e710b60555f4c35b6a4a3995", + "reference": "c2c859ceb05c3f42e710b60555f4c35b6a4a3995", "shasum": "" }, "require": { @@ -1835,22 +1836,22 @@ ], "support": { "issues": "https://github.com/http-interop/http-factory-guzzle/issues", - "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0" + "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.1" }, - "time": "2021-07-21T13:50:14+00:00" + "time": "2025-12-15T11:28:16+00:00" }, { "name": "inertiajs/inertia-laravel", - "version": "v2.0.11", + "version": "v2.0.19", "source": { "type": "git", "url": "https://github.com/inertiajs/inertia-laravel.git", - "reference": "041e148b3228407b5abe584a4f02df2651ab4d85" + "reference": "732a991342a0f82653a935440e2f3b9be1eb6f6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/041e148b3228407b5abe584a4f02df2651ab4d85", - "reference": "041e148b3228407b5abe584a4f02df2651ab4d85", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/732a991342a0f82653a935440e2f3b9be1eb6f6e", + "reference": "732a991342a0f82653a935440e2f3b9be1eb6f6e", "shasum": "" }, "require": { @@ -1905,9 +1906,9 @@ ], "support": { "issues": "https://github.com/inertiajs/inertia-laravel/issues", - "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.11" + "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.19" }, - "time": "2025-11-26T23:07:12+00:00" + "time": "2026-01-13T15:29:20+00:00" }, { "name": "lab404/laravel-impersonate", @@ -2064,92 +2065,30 @@ ], "time": "2025-10-12T15:31:36+00:00" }, - { - "name": "larabug/larabug", - "version": "3.3", - "source": { - "type": "git", - "url": "https://github.com/LaraBug/LaraBug.git", - "reference": "e9dba4d38166372f3772b57f39296eb502c598d1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/LaraBug/LaraBug/zipball/e9dba4d38166372f3772b57f39296eb502c598d1", - "reference": "e9dba4d38166372f3772b57f39296eb502c598d1", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.0.2 || ^7.0", - "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", - "nesbot/carbon": "^2.62.1 || ^3.0", - "php": "^7.4 || ^8.0 || ^8.2 || ^8.3 || ^8.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.4", - "mockery/mockery": "^1.3.3 || ^1.4.2", - "orchestra/testbench": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", - "phpunit/phpunit": "^8.5.23 || ^9.5.12 || ^10.0.9 || ^11.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "LaraBug\\ServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "LaraBug\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nathan Geerinck", - "email": "nathan@intilli.be", - "role": "Owner" - } - ], - "description": "Laravel 6.x/7.x/8.x/9.x/10.x/11.x bug notifier", - "keywords": [ - "error", - "laravel", - "log" - ], - "support": { - "issues": "https://github.com/LaraBug/LaraBug/issues", - "source": "https://github.com/LaraBug/LaraBug/tree/3.3" - }, - "time": "2025-03-01T16:12:03+00:00" - }, { "name": "laravel/fortify", - "version": "v1.32.1", + "version": "v1.34.1", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "26db37ed915770e5f0f91f6b7e153d9b3e6c09e8" + "reference": "412575e9c0cb21d49a30b7045ad4902019f538c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/26db37ed915770e5f0f91f6b7e153d9b3e6c09e8", - "reference": "26db37ed915770e5f0f91f6b7e153d9b3e6c09e8", + "url": "https://api.github.com/repos/laravel/fortify/zipball/412575e9c0cb21d49a30b7045ad4902019f538c2", + "reference": "412575e9c0cb21d49a30b7045ad4902019f538c2", "shasum": "" }, "require": { "bacon/bacon-qr-code": "^3.0", "ext-json": "*", - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/console": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "php": "^8.1", - "pragmarx/google2fa": "^9.0", - "symfony/console": "^6.0|^7.0" + "pragmarx/google2fa": "^9.0" }, "require-dev": { - "orchestra/testbench": "^8.36|^9.15|^10.8", + "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0", "phpstan/phpstan": "^1.10" }, "type": "library", @@ -2187,20 +2126,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2025-11-21T01:53:28+00:00" + "time": "2026-02-03T06:55:55+00:00" }, { "name": "laravel/framework", - "version": "v12.41.1", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "3e229b05935fd0300c632fb1f718c73046d664fc" + "reference": "174ffed91d794a35a541a5eb7c3785a02a34aaba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/3e229b05935fd0300c632fb1f718c73046d664fc", - "reference": "3e229b05935fd0300c632fb1f718c73046d664fc", + "url": "https://api.github.com/repos/laravel/framework/zipball/174ffed91d794a35a541a5eb7c3785a02a34aaba", + "reference": "174ffed91d794a35a541a5eb7c3785a02a34aaba", "shasum": "" }, "require": { @@ -2288,6 +2227,7 @@ "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", @@ -2312,7 +2252,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.8.0", + "orchestra/testbench-core": "^10.9.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -2374,6 +2314,7 @@ "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], @@ -2382,7 +2323,8 @@ "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", "src/Illuminate/Collections/", - "src/Illuminate/Conditionable/" + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" ] } }, @@ -2406,20 +2348,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-03T01:02:13+00:00" + "time": "2026-02-04T18:34:13+00:00" }, { "name": "laravel/horizon", - "version": "v5.40.2", + "version": "v5.43.0", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "005e5638478db9e25f7ae5cfb30c56846fbad793" + "reference": "2a04285ba83915511afbe987cbfedafdc27fd2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/005e5638478db9e25f7ae5cfb30c56846fbad793", - "reference": "005e5638478db9e25f7ae5cfb30c56846fbad793", + "url": "https://api.github.com/repos/laravel/horizon/zipball/2a04285ba83915511afbe987cbfedafdc27fd2de", + "reference": "2a04285ba83915511afbe987cbfedafdc27fd2de", "shasum": "" }, "require": { @@ -2483,9 +2425,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.40.2" + "source": "https://github.com/laravel/horizon/tree/v5.43.0" }, - "time": "2025-11-28T20:12:12+00:00" + "time": "2026-01-15T15:10:56+00:00" }, { "name": "laravel/jetstream", @@ -2555,16 +2497,16 @@ }, { "name": "laravel/octane", - "version": "v2.13.2", + "version": "v2.13.5", "source": { "type": "git", "url": "https://github.com/laravel/octane.git", - "reference": "5b963d2da879f2cad3a84f22bafd3d8be7170988" + "reference": "c343716659c280a7613a0c10d3241215512355ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/octane/zipball/5b963d2da879f2cad3a84f22bafd3d8be7170988", - "reference": "5b963d2da879f2cad3a84f22bafd3d8be7170988", + "url": "https://api.github.com/repos/laravel/octane/zipball/c343716659c280a7613a0c10d3241215512355ee", + "reference": "c343716659c280a7613a0c10d3241215512355ee", "shasum": "" }, "require": { @@ -2641,34 +2583,34 @@ "issues": "https://github.com/laravel/octane/issues", "source": "https://github.com/laravel/octane" }, - "time": "2025-11-28T20:13:00+00:00" + "time": "2026-01-22T17:24:46+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.8", + "version": "v0.3.12", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "ext-mbstring": "*", "php": "^8.1", - "symfony/console": "^6.2|^7.0" + "symfony/console": "^6.2|^7.0|^8.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0|^12.0", + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4|^4.0", "phpstan/phpstan": "^1.12.28", @@ -2698,22 +2640,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.8" + "source": "https://github.com/laravel/prompts/tree/v0.3.12" }, - "time": "2025-11-21T20:52:52+00:00" + "time": "2026-02-03T06:57:26+00:00" }, { "name": "laravel/sanctum", - "version": "v4.2.1", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664" + "reference": "c978c82b2b8ab685468a7ca35224497d541b775a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/f5fb373be39a246c74a060f2cf2ae2c2145b3664", - "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/c978c82b2b8ab685468a7ca35224497d541b775a", + "reference": "c978c82b2b8ab685468a7ca35224497d541b775a", "shasum": "" }, "require": { @@ -2763,20 +2705,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2025-11-21T13:59:03+00:00" + "time": "2026-01-22T22:27:01+00:00" }, { "name": "laravel/scout", - "version": "v10.22.1", + "version": "v10.23.1", "source": { "type": "git", "url": "https://github.com/laravel/scout.git", - "reference": "13ed8e0eeaddd894bf360b85cb873980de19dbaf" + "reference": "ef12fcef311421fe9f2e2425416daa17c45b8ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/scout/zipball/13ed8e0eeaddd894bf360b85cb873980de19dbaf", - "reference": "13ed8e0eeaddd894bf360b85cb873980de19dbaf", + "url": "https://api.github.com/repos/laravel/scout/zipball/ef12fcef311421fe9f2e2425416daa17c45b8ae1", + "reference": "ef12fcef311421fe9f2e2425416daa17c45b8ae1", "shasum": "" }, "require": { @@ -2843,31 +2785,31 @@ "issues": "https://github.com/laravel/scout/issues", "source": "https://github.com/laravel/scout" }, - "time": "2025-11-25T15:19:35+00:00" + "time": "2026-01-28T03:55:17+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.7", + "version": "v2.0.9", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "^2.67|^3.0", "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^6.2.0|^7.0.0" + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, "type": "library", "extra": { @@ -2904,25 +2846,25 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-11-21T20:52:36+00:00" + "time": "2026-02-03T06:55:34+00:00" }, { "name": "laravel/socialite", - "version": "v5.23.2", + "version": "v5.24.2", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "41e65d53762d33d617bf0253330d672cb95e624b" + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/41e65d53762d33d617bf0253330d672cb95e624b", - "reference": "41e65d53762d33d617bf0253330d672cb95e624b", + "url": "https://api.github.com/repos/laravel/socialite/zipball/5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", "shasum": "" }, "require": { "ext-json": "*", - "firebase/php-jwt": "^6.4", + "firebase/php-jwt": "^6.4|^7.0", "guzzlehttp/guzzle": "^6.0|^7.0", "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", @@ -2976,20 +2918,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2025-11-21T14:00:38+00:00" + "time": "2026-01-10T16:07:28+00:00" }, { "name": "laravel/tinker", - "version": "v2.10.2", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3d34b97c9a1747a81a3fde90482c092bd8b66468", + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468", "shasum": "" }, "require": { @@ -2998,7 +2940,7 @@ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.11.1|^0.12.0", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", @@ -3040,9 +2982,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.2" + "source": "https://github.com/laravel/tinker/tree/v2.11.0" }, - "time": "2025-11-20T16:29:12+00:00" + "time": "2025-12-19T19:16:45+00:00" }, { "name": "league/commonmark", @@ -3235,16 +3177,16 @@ }, { "name": "league/flysystem", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { @@ -3312,22 +3254,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2025-11-10T17:13:11+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.30.1", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5" + "reference": "e36a2bc60b06332c92e4435047797ded352b446f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d286e896083bed3190574b8b088b557b59eb66f5", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/e36a2bc60b06332c92e4435047797ded352b446f", + "reference": "e36a2bc60b06332c92e4435047797ded352b446f", "shasum": "" }, "require": { @@ -3367,22 +3309,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.30.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.31.0" }, - "time": "2025-10-20T15:27:33+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/flysystem-local", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { @@ -3416,22 +3358,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-11-10T11:23:37+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/flysystem-ziparchive", - "version": "3.29.0", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-ziparchive.git", - "reference": "7f1a2c5655be4c6e0d45574153bb5753c8afa4b7" + "reference": "ab6f9041b61ef517bc3e81891a4969181a39c17a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-ziparchive/zipball/7f1a2c5655be4c6e0d45574153bb5753c8afa4b7", - "reference": "7f1a2c5655be4c6e0d45574153bb5753c8afa4b7", + "url": "https://api.github.com/repos/thephpleague/flysystem-ziparchive/zipball/ab6f9041b61ef517bc3e81891a4969181a39c17a", + "reference": "ab6f9041b61ef517bc3e81891a4969181a39c17a", "shasum": "" }, "require": { @@ -3465,9 +3407,9 @@ "zip" ], "support": { - "source": "https://github.com/thephpleague/flysystem-ziparchive/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-ziparchive/tree/3.31.0" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/mime-type-detection", @@ -3603,20 +3545,20 @@ }, { "name": "league/uri", - "version": "7.6.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "f625804987a0a9112d954f9209d91fec52182344" + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", - "reference": "f625804987a0a9112d954f9209d91fec52182344", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.6", + "league/uri-interfaces": "^7.8", "php": "^8.1", "psr/http-factory": "^1" }, @@ -3630,11 +3572,11 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-uri": "to use the PHP native URI class", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3689,7 +3631,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.6.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.0" }, "funding": [ { @@ -3697,20 +3639,20 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-interfaces", - "version": "7.6.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", "shasum": "" }, "require": { @@ -3723,7 +3665,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3773,7 +3715,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" }, "funding": [ { @@ -3781,20 +3723,20 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2026-01-15T06:54:53+00:00" }, { "name": "maennchen/zipstream-php", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", "shasum": "" }, "require": { @@ -3805,7 +3747,7 @@ "require-dev": { "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", + "friendsofphp/php-cs-fixer": "^3.86", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", @@ -3851,7 +3793,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" }, "funding": [ { @@ -3859,7 +3801,7 @@ "type": "github" } ], - "time": "2025-07-17T11:15:13+00:00" + "time": "2025-12-10T09:58:31+00:00" }, { "name": "maize-tech/laravel-markable", @@ -4022,29 +3964,28 @@ }, { "name": "mobiledetect/mobiledetectlib", - "version": "4.8.09", + "version": "4.8.10", "source": { "type": "git", "url": "https://github.com/serbanghita/Mobile-Detect.git", - "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209" + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/a06fe2e546a06bb8c2639d6823d5250b2efb3209", - "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96b1e1fa9a968de7660a031106ab529f659d0192", + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192", "shasum": "" }, "require": { "php": ">=8.0", - "psr/cache": "^3.0", "psr/simple-cache": "^3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.65.0", + "friendsofphp/php-cs-fixer": "^v3.75.0", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.12.x-dev", - "phpunit/phpunit": "^9.6.18", - "squizlabs/php_codesniffer": "^3.11.1" + "phpstan/phpstan": "^2.1.11", + "phpunit/phpunit": "^9.6.22", + "squizlabs/php_codesniffer": "^3.12.1" }, "type": "library", "autoload": { @@ -4075,7 +4016,7 @@ ], "support": { "issues": "https://github.com/serbanghita/Mobile-Detect/issues", - "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.09" + "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.10" }, "funding": [ { @@ -4083,20 +4024,20 @@ "type": "github" } ], - "time": "2024-12-10T15:32:06+00:00" + "time": "2026-01-09T16:21:59+00:00" }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -4114,7 +4055,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -4174,7 +4115,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -4186,7 +4127,7 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "mpociot/versionable", @@ -4322,16 +4263,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.0", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + "reference": "f438fcc98f92babee98381d399c65336f3a3827f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f", "shasum": "" }, "require": { @@ -4355,7 +4296,7 @@ "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.22", "phpunit/phpunit": "^10.5.53", - "squizlabs/php_codesniffer": "^3.13.4" + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" @@ -4398,14 +4339,14 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", "issues": "https://github.com/CarbonPHP/carbon/issues", "source": "https://github.com/CarbonPHP/carbon" }, @@ -4423,20 +4364,20 @@ "type": "tidelift" } ], - "time": "2025-12-02T21:04:28+00:00" + "time": "2026-01-29T09:26:29+00:00" }, { "name": "nette/schema", - "version": "v1.3.3", + "version": "v1.3.4", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", "shasum": "" }, "require": { @@ -4444,8 +4385,8 @@ "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^2.0@stable", + "nette/tester": "^2.6", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -4486,22 +4427,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.3" + "source": "https://github.com/nette/schema/tree/v1.3.4" }, - "time": "2025-10-30T22:57:59+00:00" + "time": "2026-02-08T02:54:00+00:00" }, { "name": "nette/utils", - "version": "v4.1.0", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "url": "https://api.github.com/repos/nette/utils/zipball/f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", "shasum": "" }, "require": { @@ -4514,7 +4455,7 @@ "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -4575,22 +4516,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.0" + "source": "https://github.com/nette/utils/tree/v4.1.2" }, - "time": "2025-12-01T17:49:23+00:00" + "time": "2026-02-03T17:21:09+00:00" }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -4633,9 +4574,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "nunomaduro/termwind", @@ -4927,6 +4868,235 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "pear/archive_tar", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/pear/Archive_Tar.git", + "reference": "dc3285537f1832da8ddbbe45f5a007248b6cc00e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/dc3285537f1832da8ddbbe45f5a007248b6cc00e", + "reference": "dc3285537f1832da8ddbbe45f5a007248b6cc00e", + "shasum": "" + }, + "require": { + "pear/pear-core-minimal": "^1.10.0alpha2", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-bz2": "Bz2 compression support.", + "ext-xz": "Lzma2 compression support.", + "ext-zlib": "Gzip compression support." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Archive_Tar": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Vincent Blavet", + "email": "vincent@phpconcept.net" + }, + { + "name": "Greg Beaver", + "email": "greg@chiaraquartet.net" + }, + { + "name": "Michiel Rook", + "email": "mrook@php.net" + } + ], + "description": "Tar file management class with compression support (gzip, bzip2, lzma2)", + "homepage": "https://github.com/pear/Archive_Tar", + "keywords": [ + "archive", + "tar" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar", + "source": "https://github.com/pear/Archive_Tar" + }, + "time": "2025-07-19T14:49:16+00:00" + }, + { + "name": "pear/console_getopt", + "version": "v1.4.3", + "source": { + "type": "git", + "url": "https://github.com/pear/Console_Getopt.git", + "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/a41f8d3e668987609178c7c4a9fe48fecac53fa0", + "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Console": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Andrei Zmievski", + "email": "andrei@php.net", + "role": "Lead" + }, + { + "name": "Stig Bakken", + "email": "stig@php.net", + "role": "Developer" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net", + "role": "Helper" + } + ], + "description": "More info available on: http://pear.php.net/package/Console_Getopt", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt", + "source": "https://github.com/pear/Console_Getopt" + }, + "time": "2019-11-20T18:27:48+00:00" + }, + { + "name": "pear/pear-core-minimal", + "version": "v1.10.18", + "source": { + "type": "git", + "url": "https://github.com/pear/pear-core-minimal.git", + "reference": "c7b55789d01de0ce090d289b73f1bbd6a2f113b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/c7b55789d01de0ce090d289b73f1bbd6a2f113b1", + "reference": "c7b55789d01de0ce090d289b73f1bbd6a2f113b1", + "shasum": "" + }, + "require": { + "pear/console_getopt": "~1.4", + "pear/pear_exception": "~1.0", + "php": ">=5.4" + }, + "replace": { + "rsky/pear-core-min": "self.version" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "src/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@php.net", + "role": "Lead" + } + ], + "description": "Minimal set of PEAR core files to be used as composer dependency", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", + "source": "https://github.com/pear/pear-core-minimal" + }, + "time": "2025-12-14T20:37:07+00:00" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "time": "2021-03-21T15:43:46+00:00" + }, { "name": "php-http/discovery", "version": "1.20.0", @@ -5008,16 +5178,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { @@ -5067,7 +5237,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -5079,20 +5249,20 @@ "type": "tidelift" } ], - "time": "2025-08-21T11:53:16+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.47", + "version": "3.0.49", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" + "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9", + "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9", "shasum": "" }, "require": { @@ -5173,7 +5343,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49" }, "funding": [ { @@ -5189,39 +5359,86 @@ "type": "tidelift" } ], - "time": "2025-10-06T01:07:24+00:00" + "time": "2026-01-27T09:17:28+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.11", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", + "nikic/php-parser": "^5.7.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/lines-of-code": "^3.0.1", "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.2.3" + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -5259,7 +5476,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { @@ -5279,32 +5496,32 @@ "type": "tidelift" } ], - "time": "2025-08-27T14:37:49+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -5332,15 +5549,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-text-template", @@ -5979,16 +6208,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.15", + "version": "v0.12.19", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c" + "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/38953bc71491c838fcb6ebcbdc41ab7483cd549c", - "reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a4f766e5c5b6773d8399711019bb7d90875a50ee", + "reference": "a4f766e5c5b6773d8399711019bb7d90875a50ee", "shasum": "" }, "require": { @@ -5996,8 +6225,8 @@ "ext-tokenizer": "*", "nikic/php-parser": "^5.0 || ^4.0", "php": "^8.0 || ^7.4", - "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -6052,9 +6281,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.15" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.19" }, - "time": "2025-11-28T00:00:14+00:00" + "time": "2026-01-30T17:33:13+00:00" }, { "name": "ralouphie/getallheaders", @@ -6178,20 +6407,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -6250,9 +6479,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6630,68 +6859,18 @@ }, "time": "2025-02-24T19:33:30+00:00" }, - { - "name": "socialiteproviders/orcid", - "version": "5.1.0", - "source": { - "type": "git", - "url": "https://github.com/SocialiteProviders/Orcid.git", - "reference": "9a61194bd23394b851ef6d879991ebc284cd74d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Orcid/zipball/9a61194bd23394b851ef6d879991ebc284cd74d8", - "reference": "9a61194bd23394b851ef6d879991ebc284cd74d8", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^8.0", - "socialiteproviders/manager": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "SocialiteProviders\\Orcid\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Cornwell", - "email": "ben@bencornwell.com" - } - ], - "description": "ORCID OAuth2 Provider for Laravel Socialite", - "keywords": [ - "laravel", - "oauth", - "orcid", - "provider", - "socialite" - ], - "support": { - "docs": "https://socialiteproviders.com/orcid", - "issues": "https://github.com/socialiteproviders/providers/issues", - "source": "https://github.com/socialiteproviders/providers" - }, - "time": "2024-02-01T18:57:00+00:00" - }, { "name": "spatie/db-dumper", - "version": "3.8.1", + "version": "3.8.3", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "e974cc7862b8de1bd3b7ea7d4839ba7167acb546" + "reference": "eac3221fbe27fac51f388600d27b67b1b079406e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/e974cc7862b8de1bd3b7ea7d4839ba7167acb546", - "reference": "e974cc7862b8de1bd3b7ea7d4839ba7167acb546", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/eac3221fbe27fac51f388600d27b67b1b079406e", + "reference": "eac3221fbe27fac51f388600d27b67b1b079406e", "shasum": "" }, "require": { @@ -6729,7 +6908,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.8.1" + "source": "https://github.com/spatie/db-dumper/tree/3.8.3" }, "funding": [ { @@ -6741,7 +6920,7 @@ "type": "github" } ], - "time": "2025-11-26T09:51:23+00:00" + "time": "2026-01-05T16:26:03+00:00" }, { "name": "spatie/eloquent-sortable", @@ -6819,16 +6998,16 @@ }, { "name": "spatie/laravel-backup", - "version": "9.3.7", + "version": "9.4.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-backup.git", - "reference": "6aa2d0ef42218ba6c1f627a17ade3e1ffd0e18af" + "reference": "3ab3f29f0054655ebfdd5b2f6479de0f8f62f5c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/6aa2d0ef42218ba6c1f627a17ade3e1ffd0e18af", - "reference": "6aa2d0ef42218ba6c1f627a17ade3e1ffd0e18af", + "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/3ab3f29f0054655ebfdd5b2f6479de0f8f62f5c2", + "reference": "3ab3f29f0054655ebfdd5b2f6479de0f8f62f5c2", "shasum": "" }, "require": { @@ -6903,7 +7082,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-backup/issues", - "source": "https://github.com/spatie/laravel-backup/tree/9.3.7" + "source": "https://github.com/spatie/laravel-backup/tree/9.4.0" }, "funding": [ { @@ -6915,7 +7094,7 @@ "type": "other" } ], - "time": "2025-11-26T15:43:43+00:00" + "time": "2026-01-31T13:16:56+00:00" }, { "name": "spatie/laravel-cookie-consent", @@ -6995,6 +7174,91 @@ ], "time": "2025-02-21T13:11:14+00:00" }, + { + "name": "spatie/laravel-csp", + "version": "3.22.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-csp.git", + "reference": "d0a4f1e970f4b1d81bed4afe8c1125d2c390f73d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-csp/zipball/d0a4f1e970f4b1d81bed4afe8c1125d2c390f73d", + "reference": "d0a4f1e970f4b1d81bed4afe8c1125d2c390f73d", + "shasum": "" + }, + "require": { + "illuminate/http": "^11.36.1|^12.0", + "illuminate/support": "^11.36.1|^12.0", + "php": "^8.3", + "spatie/laravel-package-tools": "^1.17" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.9|^10.0", + "pestphp/pest": "^3.0", + "roave/security-advisories": "dev-master" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Csp\\CspServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Csp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Verhelst", + "email": "tvke91@gmail.com", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Add CSP headers to the responses of a Laravel app", + "homepage": "https://github.com/spatie/laravel-csp", + "keywords": [ + "content-security-policy", + "csp", + "headers", + "laravel", + "laravel-csp", + "security", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-csp/tree/3.22.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2026-01-24T18:56:15+00:00" + }, { "name": "spatie/laravel-honeypot", "version": "4.6.2", @@ -7134,16 +7398,16 @@ }, { "name": "spatie/laravel-permission", - "version": "6.23.0", + "version": "6.24.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb" + "reference": "76adb1fc8d07c16a0721c35c4cc330b7a12598d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/9e41247bd512b1e6c229afbc1eb528f7565ae3bb", - "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/76adb1fc8d07c16a0721c35c4cc330b7a12598d7", + "reference": "76adb1fc8d07c16a0721c35c4cc330b7a12598d7", "shasum": "" }, "require": { @@ -7205,7 +7469,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/6.23.0" + "source": "https://github.com/spatie/laravel-permission/tree/6.24.0" }, "funding": [ { @@ -7213,20 +7477,20 @@ "type": "github" } ], - "time": "2025-11-03T20:16:13+00:00" + "time": "2025-12-13T21:45:21+00:00" }, { "name": "spatie/laravel-query-builder", - "version": "6.3.6", + "version": "6.4.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481" + "reference": "9c7427c0dff87d7232beeecd2d33bf7d21952b43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481", - "reference": "b9a5af31c79ae8fb79ae0aa8ba2b9e7180f39481", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/9c7427c0dff87d7232beeecd2d33bf7d21952b43", + "reference": "9c7427c0dff87d7232beeecd2d33bf7d21952b43", "shasum": "" }, "require": { @@ -7287,7 +7551,7 @@ "type": "custom" } ], - "time": "2025-10-20T09:50:21+00:00" + "time": "2026-01-27T07:27:24+00:00" }, { "name": "spatie/laravel-signal-aware-command", @@ -7746,16 +8010,16 @@ }, { "name": "spatie/temporary-directory", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + "reference": "662e481d6ec07ef29fd05010433428851a42cd07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", - "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07", + "reference": "662e481d6ec07ef29fd05010433428851a42cd07", "shasum": "" }, "require": { @@ -7791,7 +8055,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + "source": "https://github.com/spatie/temporary-directory/tree/2.3.1" }, "funding": [ { @@ -7803,20 +8067,20 @@ "type": "github" } ], - "time": "2025-01-13T13:04:43+00:00" + "time": "2026-01-12T07:42:22+00:00" }, { "name": "swagger-api/swagger-ui", - "version": "v5.30.3", + "version": "v5.31.0", "source": { "type": "git", "url": "https://github.com/swagger-api/swagger-ui.git", - "reference": "199761a94d03753ec62c23bb4ba162bb73c3cfc7" + "reference": "61dddd44389bf25922639afd682f50b8a0fb5cf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/199761a94d03753ec62c23bb4ba162bb73c3cfc7", - "reference": "199761a94d03753ec62c23bb4ba162bb73c3cfc7", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/61dddd44389bf25922639afd682f50b8a0fb5cf4", + "reference": "61dddd44389bf25922639afd682f50b8a0fb5cf4", "shasum": "" }, "type": "library", @@ -7862,28 +8126,27 @@ ], "support": { "issues": "https://github.com/swagger-api/swagger-ui/issues", - "source": "https://github.com/swagger-api/swagger-ui/tree/v5.30.3" + "source": "https://github.com/swagger-api/swagger-ui/tree/v5.31.0" }, - "time": "2025-11-25T12:39:47+00:00" + "time": "2025-12-11T15:57:42+00:00" }, { "name": "symfony/clock", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "php": ">=8.4", + "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -7922,7 +8185,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "source": "https://github.com/symfony/clock/tree/v8.0.0" }, "funding": [ { @@ -7942,20 +8205,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2025-11-12T15:46:48+00:00" }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -8020,7 +8283,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -8040,24 +8303,24 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/6225bd458c53ecdee056214cb4a2ffaf58bd592b", + "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -8089,7 +8352,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v8.0.0" }, "funding": [ { @@ -8109,7 +8372,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2025-10-30T14:17:19+00:00" }, { "name": "symfony/deprecation-contracts", @@ -8180,16 +8443,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", "shasum": "" }, "require": { @@ -8238,7 +8501,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" }, "funding": [ { @@ -8258,28 +8521,28 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2026-01-20T16:42:42+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/security-http": "<7.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -8288,14 +8551,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8323,7 +8586,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" }, "funding": [ { @@ -8343,7 +8606,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2026-01-05T11:45:55+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -8421,18 +8684,88 @@ ], "time": "2024-09-25T14:21:43+00:00" }, + { + "name": "symfony/filesystem", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-01T09:13:36+00:00" + }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -8467,7 +8800,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -8487,20 +8820,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b" + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/769c1720b68e964b13b58529c17d4a385c62167b", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", "shasum": "" }, "require": { @@ -8549,7 +8882,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" }, "funding": [ { @@ -8569,20 +8902,20 @@ "type": "tidelift" } ], - "time": "2025-11-13T08:49:24+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7348193cd384495a755554382e4526f27c456085" + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7348193cd384495a755554382e4526f27c456085", - "reference": "7348193cd384495a755554382e4526f27c456085", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", "shasum": "" }, "require": { @@ -8668,7 +9001,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" }, "funding": [ { @@ -8688,20 +9021,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:38:24+00:00" + "time": "2026-01-28T10:33:42+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", "shasum": "" }, "require": { @@ -8752,7 +9085,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.4" }, "funding": [ { @@ -8772,20 +9105,20 @@ "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2026-01-08T08:25:11+00:00" }, { "name": "symfony/mime", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { @@ -8796,15 +9129,15 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -8841,7 +9174,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -8861,7 +9194,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -9774,16 +10107,16 @@ }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { @@ -9815,7 +10148,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -9835,20 +10168,20 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "0101ff8bd0506703b045b1670960302d302a726c" + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/0101ff8bd0506703b045b1670960302d302a726c", - "reference": "0101ff8bd0506703b045b1670960302d302a726c", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/929ffe10bbfbb92e711ac3818d416f9daffee067", + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067", "shasum": "" }, "require": { @@ -9903,7 +10236,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.4" }, "funding": [ { @@ -9923,20 +10256,20 @@ "type": "tidelift" } ], - "time": "2025-11-13T08:38:49+00:00" + "time": "2026-01-03T23:30:35+00:00" }, { "name": "symfony/routing", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4720254cb2644a0b876233d258a32bf017330db7" + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", - "reference": "4720254cb2644a0b876233d258a32bf017330db7", + "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", "shasum": "" }, "require": { @@ -9988,7 +10321,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.0" + "source": "https://github.com/symfony/routing/tree/v7.4.4" }, "funding": [ { @@ -10008,7 +10341,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-12T12:19:02+00:00" }, { "name": "symfony/service-contracts", @@ -10099,35 +10432,34 @@ }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "758b372d6882506821ed666032e43020c4f57194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -10166,7 +10498,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v8.0.4" }, "funding": [ { @@ -10186,38 +10518,31 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-12T12:37:40+00:00" }, { "name": "symfony/translation", - "version": "v7.4.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + "reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "url": "https://api.github.com/repos/symfony/translation/zipball/db70c8ce7db74fd2da7b1d268db46b2a8ce32c10", + "reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" }, "conflict": { "nikic/php-parser": "<5.0", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<6.4", - "symfony/yaml": "<6.4" + "symfony/service-contracts": "<2.5" }, "provide": { "symfony/translation-implementation": "2.3|3.0" @@ -10225,17 +10550,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -10266,7 +10591,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.0" + "source": "https://github.com/symfony/translation/tree/v8.0.4" }, "funding": [ { @@ -10286,7 +10611,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-13T13:06:50+00:00" }, { "name": "symfony/translation-contracts", @@ -10372,16 +10697,16 @@ }, { "name": "symfony/uid", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", "shasum": "" }, "require": { @@ -10426,7 +10751,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.0" + "source": "https://github.com/symfony/uid/tree/v7.4.4" }, "funding": [ { @@ -10446,20 +10771,20 @@ "type": "tidelift" } ], - "time": "2025-09-25T11:02:55+00:00" + "time": "2026-01-03T23:30:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", "shasum": "" }, "require": { @@ -10513,7 +10838,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" }, "funding": [ { @@ -10533,20 +10858,20 @@ "type": "tidelift" } ], - "time": "2025-10-27T20:36:44+00:00" + "time": "2026-01-01T22:13:48+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { @@ -10589,7 +10914,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.0" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -10609,7 +10934,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { "name": "theseer/tokenizer", @@ -10733,23 +11058,23 @@ }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d" + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^7.4 || ^8.0", - "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -10782,32 +11107,32 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" }, - "time": "2024-12-21T16:25:41+00:00" + "time": "2025-12-02T11:56:42+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.6.2", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.1.4", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -10856,7 +11181,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -10868,7 +11193,7 @@ "type": "tidelift" } ], - "time": "2025-04-30T23:37:27+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "voku/portable-ascii", @@ -10944,38 +11269,111 @@ ], "time": "2024-11-21T01:49:47+00:00" }, + { + "name": "whikloj/bagittools", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/whikloj/BagItTools.git", + "reference": "30697b4692cc874bd115b1b7d031001e7fe77f30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/whikloj/BagItTools/zipball/30697b4692cc874bd115b1b7d031001e7fe77f30", + "reference": "30697b4692cc874bd115b1b7d031001e7fe77f30", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-intl": "*", + "ext-mbstring": "*", + "ext-zip": "*", + "pear/archive_tar": "^1.4.14", + "php": ">=8.2", + "symfony/console": ">7" + }, + "require-dev": { + "donatj/mock-webserver": "^2.6", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "symfony": { + "allow-contrib": false + } + }, + "autoload": { + "psr-4": { + "whikloj\\BagItTools\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jared Whiklo", + "email": "jwhiklo@gmail.com", + "role": "Developer" + } + ], + "description": "A PHP library to manipulate and verify BagIt bags.", + "homepage": "https://github.com/whikloj/bagittools", + "keywords": [ + "bagit", + "bags", + "data", + "integrity", + "transmission" + ], + "support": { + "issues": "https://github.com/whikloj/BagItTools/issues", + "source": "https://github.com/whikloj/BagItTools/tree/6.0.0" + }, + "time": "2026-01-07T22:17:43+00:00" + }, { "name": "zircote/swagger-php", - "version": "4.11.1", + "version": "5.8.1", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1" + "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/7df10e8ec47db07c031db317a25bef962b4e5de1", - "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/8adf6bb57561243aca48339e7f39bddf27ae546c", + "reference": "8adf6bb57561243aca48339e7f39bddf27ae546c", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=7.2", + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=7.4", + "phpstan/phpdoc-parser": "^2.0", "psr/log": "^1.1 || ^2.0 || ^3.0", "symfony/deprecation-contracts": "^2 || ^3", - "symfony/finder": ">=2.2", - "symfony/yaml": ">=3.3" + "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" }, "require-dev": { "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.7 || ^2.0", - "friendsofphp/php-cs-fixer": "^2.17 || 3.62.0", - "phpstan/phpstan": "^1.6", - "phpunit/phpunit": ">=8", - "vimeo/psalm": "^4.23" + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62.0", + "phpstan/phpstan": "^1.6 || ^2.0", + "phpunit/phpunit": "^9.0", + "rector/rector": "^1.0 || ^2.3.1", + "vimeo/psalm": "^4.30 || ^5.0" }, "suggest": { - "doctrine/annotations": "^1.7 || ^2.0" + "doctrine/annotations": "^2.0", + "radebatz/type-info-extras": "^1.0.2" }, "bin": [ "bin/openapi" @@ -10983,7 +11381,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -11011,8 +11409,8 @@ "homepage": "https://radebatz.net" } ], - "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", - "homepage": "https://github.com/zircote/swagger-php/", + "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", "keywords": [ "api", "json", @@ -11021,24 +11419,30 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/4.11.1" + "source": "https://github.com/zircote/swagger-php/tree/5.8.1" }, - "time": "2024-10-15T19:20:02+00:00" + "funding": [ + { + "url": "https://github.com/zircote", + "type": "github" + } + ], + "time": "2026-02-03T23:48:39+00:00" } ], "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.8.4", + "version": "v7.8.5", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4" + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/130a9bf0e269ee5f5b320108f794ad03e275cad4", - "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", "shasum": "" }, "require": { @@ -11046,27 +11450,27 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.2.0", + "fidry/cpu-core-counter": "^1.3.0", "jean85/pretty-package-versions": "^2.1.1", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.10", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^11.0.12", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.24", + "phpunit/phpunit": "^11.5.46", "sebastian/environment": "^7.2.1", - "symfony/console": "^6.4.22 || ^7.3.0", - "symfony/process": "^6.4.20 || ^7.3.0" + "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", + "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan": "^2.1.33", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", - "squizlabs/php_codesniffer": "^3.13.2", - "symfony/filesystem": "^6.4.13 || ^7.3.0" + "phpstan/phpstan-phpunit": "^2.0.11", + "phpstan/phpstan-strict-rules": "^2.0.7", + "squizlabs/php_codesniffer": "^3.13.5", + "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" }, "bin": [ "bin/paratest", @@ -11106,7 +11510,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.8.4" + "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" }, "funding": [ { @@ -11118,7 +11522,7 @@ "type": "paypal" } ], - "time": "2025-06-23T06:07:21+00:00" + "time": "2026-01-08T08:02:38+00:00" }, { "name": "fakerphp/faker", @@ -11428,34 +11832,34 @@ }, { "name": "laravel/boost", - "version": "v1.8.3", + "version": "v1.8.10", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "26572e858e67334952779c0110ca4c378a44d28d" + "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/26572e858e67334952779c0110ca4c378a44d28d", - "reference": "26572e858e67334952779c0110ca4c378a44d28d", + "url": "https://api.github.com/repos/laravel/boost/zipball/aad8b2a423b0a886c2ce7ee92abbfde69992ff32", + "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.9", - "illuminate/console": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/routing": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/support": "^10.49.0|^11.45.3|^12.28.1", - "laravel/mcp": "^0.3.4", + "illuminate/console": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/support": "^10.49.0|^11.45.3|^12.41.1", + "laravel/mcp": "^0.5.1", "laravel/prompts": "0.1.25|^0.3.6", "laravel/roster": "^0.2.9", "php": "^8.1" }, "require-dev": { - "laravel/pint": "1.20", + "laravel/pint": "^1.20.0", "mockery/mockery": "^1.6.12", "orchestra/testbench": "^8.36.0|^9.15.0|^10.6", - "pestphp/pest": "^2.36.0|^3.8.4", + "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5", "phpstan/phpstan": "^2.1.27", "rector/rector": "^2.1" }, @@ -11490,39 +11894,39 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2025-11-26T14:12:52+00:00" + "time": "2026-01-14T14:51:16+00:00" }, { "name": "laravel/mcp", - "version": "v0.3.4", + "version": "v0.5.5", "source": { "type": "git", "url": "https://github.com/laravel/mcp.git", - "reference": "0b86fb613a0df971cec89271c674a677c2cb4f77" + "reference": "b3327bb75fd2327577281e507e2dbc51649513d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/mcp/zipball/0b86fb613a0df971cec89271c674a677c2cb4f77", - "reference": "0b86fb613a0df971cec89271c674a677c2cb4f77", + "url": "https://api.github.com/repos/laravel/mcp/zipball/b3327bb75fd2327577281e507e2dbc51649513d6", + "reference": "b3327bb75fd2327577281e507e2dbc51649513d6", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "illuminate/console": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/container": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/http": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/json-schema": "^12.28.1", - "illuminate/routing": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/support": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/validation": "^10.49.0|^11.45.3|^12.28.1", - "php": "^8.1" + "illuminate/console": "^11.45.3|^12.41.1|^13.0", + "illuminate/container": "^11.45.3|^12.41.1|^13.0", + "illuminate/contracts": "^11.45.3|^12.41.1|^13.0", + "illuminate/http": "^11.45.3|^12.41.1|^13.0", + "illuminate/json-schema": "^12.41.1|^13.0", + "illuminate/routing": "^11.45.3|^12.41.1|^13.0", + "illuminate/support": "^11.45.3|^12.41.1|^13.0", + "illuminate/validation": "^11.45.3|^12.41.1|^13.0", + "php": "^8.2" }, "require-dev": { - "laravel/pint": "1.20.0", - "orchestra/testbench": "^8.36.0|^9.15.0|^10.6.0", - "pestphp/pest": "^2.36.0|^3.8.4|^4.1.0", + "laravel/pint": "^1.20", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "pestphp/pest": "^3.8.5|^4.3.2", "phpstan/phpstan": "^2.1.27", "rector/rector": "^2.2.4" }, @@ -11563,41 +11967,42 @@ "issues": "https://github.com/laravel/mcp/issues", "source": "https://github.com/laravel/mcp" }, - "time": "2025-11-18T14:41:05+00:00" + "time": "2026-02-05T14:05:18+00:00" }, { "name": "laravel/pail", - "version": "v1.2.4", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30" + "reference": "fdb73f5eacf03db576c710d5a00101ba185f2254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/49f92285ff5d6fc09816e976a004f8dec6a0ea30", - "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30", + "url": "https://api.github.com/repos/laravel/pail/zipball/fdb73f5eacf03db576c710d5a00101ba185f2254", + "reference": "fdb73f5eacf03db576c710d5a00101ba185f2254", "shasum": "" }, "require": { "ext-mbstring": "*", - "illuminate/console": "^10.24|^11.0|^12.0", - "illuminate/contracts": "^10.24|^11.0|^12.0", - "illuminate/log": "^10.24|^11.0|^12.0", - "illuminate/process": "^10.24|^11.0|^12.0", - "illuminate/support": "^10.24|^11.0|^12.0", + "illuminate/console": "^10.24|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.24|^11.0|^12.0|^13.0", + "illuminate/log": "^10.24|^11.0|^12.0|^13.0", + "illuminate/process": "^10.24|^11.0|^12.0|^13.0", + "illuminate/support": "^10.24|^11.0|^12.0|^13.0", "nunomaduro/termwind": "^1.15|^2.0", "php": "^8.2", - "symfony/console": "^6.0|^7.0" + "symfony/console": "^6.0|^7.0|^8.0" }, "require-dev": { - "laravel/framework": "^10.24|^11.0|^12.0", + "laravel/framework": "^10.24|^11.0|^12.0|^13.0", "laravel/pint": "^1.13", - "orchestra/testbench-core": "^8.13|^9.17|^10.8", + "orchestra/testbench-core": "^8.13|^9.17|^10.8|^11.0", "pestphp/pest": "^2.20|^3.0|^4.0", "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", "phpstan/phpstan": "^1.12.27", - "symfony/var-dumper": "^6.3|^7.0" + "symfony/var-dumper": "^6.3|^7.0|^8.0", + "symfony/yaml": "^6.3|^7.0|^8.0" }, "type": "library", "extra": { @@ -11642,20 +12047,20 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2025-11-20T16:29:35+00:00" + "time": "2026-02-04T15:10:32+00:00" }, { "name": "laravel/pint", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", - "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", + "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", "shasum": "" }, "require": { @@ -11666,9 +12071,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.90.0", - "illuminate/view": "^12.40.1", - "larastan/larastan": "^3.8.0", + "friendsofphp/php-cs-fixer": "^3.92.4", + "illuminate/view": "^12.44.0", + "larastan/larastan": "^3.8.1", "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.3", @@ -11709,7 +12114,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-11-25T21:15:52+00:00" + "time": "2026-01-05T16:49:17+00:00" }, { "name": "laravel/roster", @@ -11774,16 +12179,16 @@ }, { "name": "laravel/sail", - "version": "v1.49.0", + "version": "v1.52.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "070c7f34ca8dbece4350fbfe0bab580047dfacc7" + "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/070c7f34ca8dbece4350fbfe0bab580047dfacc7", - "reference": "070c7f34ca8dbece4350fbfe0bab580047dfacc7", + "url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3", + "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3", "shasum": "" }, "require": { @@ -11833,7 +12238,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-11-25T21:15:57+00:00" + "time": "2026-01-01T02:46:03+00:00" }, { "name": "mockery/mockery", @@ -12321,16 +12726,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.45", + "version": "11.5.52", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "faf5fff4fb9beb290affa53f812b05380819c51a" + "reference": "b287d32c26f78768e391843c5a59395f24b62605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/faf5fff4fb9beb290affa53f812b05380819c51a", - "reference": "faf5fff4fb9beb290affa53f812b05380819c51a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b287d32c26f78768e391843c5a59395f24b62605", + "reference": "b287d32c26f78768e391843c5a59395f24b62605", "shasum": "" }, "require": { @@ -12344,19 +12749,20 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.11", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.2", + "sebastian/comparator": "^6.3.3", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -12402,7 +12808,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.52" }, "funding": [ { @@ -12426,7 +12832,7 @@ "type": "tidelift" } ], - "time": "2025-12-01T07:38:43+00:00" + "time": "2026-02-08T07:05:14+00:00" }, { "name": "sebastian/cli-parser", @@ -12544,16 +12950,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.2", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { @@ -12612,7 +13018,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -12632,7 +13038,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:07:46+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/diff", @@ -13171,11 +13577,8 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.2" + "php": "^8.4" }, "platform-dev": {}, - "platform-overrides": { - "php": "8.3.28" - }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/app.php b/config/app.php new file mode 100644 index 00000000..c0525322 --- /dev/null +++ b/config/app.php @@ -0,0 +1,66 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Description + |-------------------------------------------------------------------------- + | + | A brief description of your application. + | + */ + + 'description' => env('APP_DESCRIPTION', 'NMRXIV is an open-access preprint repository for sharing and discovering nuclear magnetic resonance (NMR) spectroscopy data.'), + + /* + |-------------------------------------------------------------------------- + | Schema Version + |-------------------------------------------------------------------------- + | + | The schema version used by the application for data structures. + | + */ + + 'schema_version' => env('SCHEMA_VERSION', 'beta'), + +]; diff --git a/config/csp.php b/config/csp.php new file mode 100644 index 00000000..69592830 --- /dev/null +++ b/config/csp.php @@ -0,0 +1,86 @@ + [ + // Enforcement mode - now using secure CSP policy + App\Support\Csp\Policies\NmrxivPolicy::class, + ], + + /** + * Register additional global CSP directives here. + * These can be configured via environment variables for runtime flexibility. + */ + 'directives' => [ + // Additional connect-src domains (configurable via env) + ...(env('CSP_ADDITIONAL_CONNECT_SRC') ? [ + [Spatie\Csp\Directive::CONNECT, array_filter(explode(',', env('CSP_ADDITIONAL_CONNECT_SRC')))], + ] : []), + + // Additional img-src domains (configurable via env) + ...(env('CSP_ADDITIONAL_IMG_SRC') ? [ + [Spatie\Csp\Directive::IMG, array_filter(explode(',', env('CSP_ADDITIONAL_IMG_SRC')))], + ] : []), + + // Additional script-src domains (configurable via env) + ...(env('CSP_ADDITIONAL_SCRIPT_SRC') ? [ + [Spatie\Csp\Directive::SCRIPT, array_filter(explode(',', env('CSP_ADDITIONAL_SCRIPT_SRC')))], + ] : []), + + // Additional style-src domains (configurable via env) + ...(env('CSP_ADDITIONAL_STYLE_SRC') ? [ + [Spatie\Csp\Directive::STYLE, array_filter(explode(',', env('CSP_ADDITIONAL_STYLE_SRC')))], + ] : []), + ], + + /* + * These presets which will be put in a report-only policy. This is great for testing out + * a new policy or changes to existing CSP policy without breaking anything. + */ + 'report_only_presets' => [ + // Moved to enforcement mode above + ], + + /** + * Register additional global report-only CSP directives here. + */ + 'report_only_directives' => [ + // [Directive::SCRIPT, [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE]], + ], + + /* + * All violations against a policy will be reported to this url. + * Set to null to disable violation reporting. + */ + 'report_uri' => null, + + /* + * Headers will only be added if this setting is set to true. + */ + 'enabled' => env('CSP_ENABLED', true), + + /** + * Headers will be added when Vite is hot reloading. + */ + 'enabled_while_hot_reloading' => env('CSP_ENABLED_WHILE_HOT_RELOADING', true), + + /* + * The class responsible for generating the nonces used in inline tags and headers. + */ + 'nonce_generator' => Spatie\Csp\Nonce\RandomString::class, + + /* + * Set false to disable automatic nonce generation and handling. + * This is useful when you want to use 'unsafe-inline' for scripts/styles + * and cannot add inline nonces. + * Note that this will make your CSP policy less secure. + */ + 'nonce_enabled' => env('CSP_NONCE_ENABLED', true), +]; diff --git a/config/doi.php b/config/doi.php index 4855e6f3..d3f63ffc 100644 --- a/config/doi.php +++ b/config/doi.php @@ -14,6 +14,17 @@ 'default' => env('DOI_PROVIDER', 'datacite'), + /* + |-------------------------------------------------------------------------- + | DOI Host + |-------------------------------------------------------------------------- + | + | The DOI host URL used for resolving DOI identifiers. + | + */ + + 'host' => env('DOI_HOST', 'https://doi.org'), + /* |-------------------------------------------------------------------------- | DataCite Options diff --git a/config/external-links.php b/config/external-links.php new file mode 100644 index 00000000..46b7ce5f --- /dev/null +++ b/config/external-links.php @@ -0,0 +1,39 @@ + env('MICHI_STANDARDS_URL'), + + /* + |-------------------------------------------------------------------------- + | External API Endpoints + |-------------------------------------------------------------------------- + | + | API endpoints for external services used by the application. + | + */ + + 'nmrium_url' => env('NMRIUM_URL', 'https://nmrium.nmrxiv.org'), + 'nmrkit_url' => env('NMRKIT_URL', 'https://nodejs.nmrxiv.org'), + 'europemc_ws_api' => env('EUROPEMC_WS_API', 'https://www.ebi.ac.uk/europepmc/webservices/rest/search'), + 'cm_api' => env('CM_API', 'https://api.cheminf.studio'), + 'crossref_api' => env('CROSSREF_API', 'https://api.crossref.org/works/'), + 'datacite_api' => env('DATACITE_API', 'https://api.datacite.org'), + +]; diff --git a/config/filesystems.php b/config/filesystems.php index 1e7f28e4..9ad0e21e 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -35,6 +35,12 @@ 'serve' => true, 'throw' => false, 'report' => false, + // AWS S3 config keys for testing (when StorageSignedUrlService is used) + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'endpoint' => env('AWS_URL'), ], 'ceph' => [ diff --git a/config/larabug.php b/config/larabug.php deleted file mode 100644 index ddc0026b..00000000 --- a/config/larabug.php +++ /dev/null @@ -1,146 +0,0 @@ - env('LB_KEY', ''), - - /* - |-------------------------------------------------------------------------- - | Project key - |-------------------------------------------------------------------------- - | - | This is your project key which you receive when creating a project - | Retrieve your key from https://www.larabug.com - | - */ - - 'project_key' => env('LB_PROJECT_KEY', ''), - - /* - |-------------------------------------------------------------------------- - | Environment setting - |-------------------------------------------------------------------------- - | - | This setting determines if the exception should be send over or not. - | - */ - - 'environments' => [ - 'production', - 'development', - ], - - /* - |-------------------------------------------------------------------------- - | Project version - |-------------------------------------------------------------------------- - | - | Set the project version, default: null. - | For git repository: shell_exec("git log -1 --pretty=format:'%h' --abbrev-commit") - | - */ - 'project_version' => null, - - /* - |-------------------------------------------------------------------------- - | Lines near exception - |-------------------------------------------------------------------------- - | - | How many lines to show near exception line. The more you specify the bigger - | the displayed code will be. Max value can be 50, will be defaulted to - | 12 if higher than 50 automatically. - | - */ - - 'lines_count' => 12, - - /* - |-------------------------------------------------------------------------- - | Prevent duplicates - |-------------------------------------------------------------------------- - | - | Set the sleep time between duplicate exceptions. This value is in seconds, default: 60 seconds (1 minute) - | - */ - - 'sleep' => 60, - - /* - |-------------------------------------------------------------------------- - | Skip exceptions - |-------------------------------------------------------------------------- - | - | List of exceptions to skip sending. - | - */ - - 'except' => [ - 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException', - ], - - /* - |-------------------------------------------------------------------------- - | Key filtering - |-------------------------------------------------------------------------- - | - | Filter out these variables before sending them to LaraBug - | - */ - - 'blacklist' => [ - '*authorization*', - '*password*', - '*token*', - '*auth*', - '*verification*', - '*credit_card*', - 'cardToken', // mollie card token - '*cvv*', - '*iban*', - '*name*', - '*email*', - ], - - /* - |-------------------------------------------------------------------------- - | Release git hash - |-------------------------------------------------------------------------- - | - | - */ - - // 'release' => trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')), - - /* - |-------------------------------------------------------------------------- - | Server setting - |-------------------------------------------------------------------------- - | - | This setting allows you to change the server. - | - */ - - 'server' => env('LB_SERVER', 'https://www.larabug.com/api/log'), - - /* - |-------------------------------------------------------------------------- - | Verify SSL setting - |-------------------------------------------------------------------------- - | - | Enables / disables the SSL verification when sending exceptions to LaraBug - | Never turn SSL verification off on production instances - | - */ - 'verify_ssl' => env('LB_VERIFY_SSL', true), - -]; diff --git a/config/logging.php b/config/logging.php deleted file mode 100644 index 6348a123..00000000 --- a/config/logging.php +++ /dev/null @@ -1,11 +0,0 @@ - [ - 'larabug' => [ - 'driver' => 'larabug', - ], - ], - -]; diff --git a/config/mail.php b/config/mail.php index 1cb555c5..78b33177 100644 --- a/config/mail.php +++ b/config/mail.php @@ -22,6 +22,22 @@ ], ], + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + 'markdown' => [ 'theme' => 'default', diff --git a/config/nmrxiv.php b/config/nmrxiv.php new file mode 100644 index 00000000..2d8d5ae3 --- /dev/null +++ b/config/nmrxiv.php @@ -0,0 +1,46 @@ + (int) env('COOL_OFF_PERIOD', 30), + + /* + |-------------------------------------------------------------------------- + | Spectra Parsing Configuration + |-------------------------------------------------------------------------- + | + | Configuration for the spectra parsing queue system including API endpoints, + | storage locations, retry logic, and timeout values. + | + */ + + 'spectra_parsing' => [ + // API Endpoints + 'nmrkit_api_url' => env('NMRKIT_API_URL', 'https://nmrkit.nmrxiv.org/latest/spectra/parse/url'), + 'bioschema_api_url' => env('BIOSCHEMA_API_URL', 'https://nmrxiv.org/api/v1/schemas/bioschemas'), + + // Storage Configuration + 'storage_disk' => env('SPECTRA_STORAGE_DISK', 'local'), + 'storage_path' => env('SPECTRA_STORAGE_PATH', 'spectra_parse'), + + // Job Configuration + 'job_tries' => (int) env('SPECTRA_JOB_TRIES', 3), + 'job_timeout' => (int) env('SPECTRA_JOB_TIMEOUT', 600), + + // Network Configuration + 'retry_count' => (int) env('SPECTRA_RETRY_COUNT', 3), + 'download_timeout' => (int) env('SPECTRA_DOWNLOAD_TIMEOUT', 300), + 'api_timeout' => (int) env('SPECTRA_API_TIMEOUT', 300), + ], + +]; diff --git a/config/orcid.php b/config/orcid.php new file mode 100644 index 00000000..add89403 --- /dev/null +++ b/config/orcid.php @@ -0,0 +1,29 @@ + env('ORCID_BASE_URL', 'https://pub.orcid.org/v3.0'), + + /* + |-------------------------------------------------------------------------- + | ORCID API Endpoints + |-------------------------------------------------------------------------- + | + | Various ORCID API endpoints for searching and retrieving person data. + | + */ + + 'search_api' => env('ORCID_ID_SEARCH_API', 'https://pub.orcid.org/v3.0/search'), + 'person_api' => env('ORCID_ID_PERSON_API', 'https://pub.orcid.org/v3.0'), + 'employment_api' => env('ORCID_ID_EMPLOYMENT_API', 'https://pub.orcid.org/v3.0'), + +]; diff --git a/config/ror.php b/config/ror.php new file mode 100644 index 00000000..b245b78f --- /dev/null +++ b/config/ror.php @@ -0,0 +1,19 @@ + env('ROR_API_URL', 'https://api.ror.org/organizations'), + + 'client_id' => env('ROR_CLIENT_ID'), + +]; diff --git a/config/schemas.php b/config/schemas.php index ce06349b..6bd8f751 100644 --- a/config/schemas.php +++ b/config/schemas.php @@ -14,5 +14,7 @@ * Ontologies * */ - 'measurement_technique' => env('MEASUREMENT_TECHNIQUE', 'http://purl.obolibrary.org/obo/CHMO_0000613'), + 'ontologies' => [ + 'measurement_technique' => env('MEASUREMENT_TECHNIQUE', 'http://purl.obolibrary.org/obo/CHMO_0000613'), + ], ]; diff --git a/config/scout.php b/config/scout.php index 5c8b7d20..a2991b46 100644 --- a/config/scout.php +++ b/config/scout.php @@ -132,6 +132,7 @@ 'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY', null), + 'public_key' => env('MEILISEARCH_PUBLICKEY', null), ], ]; diff --git a/config/services.php b/config/services.php index 88e0bd2e..8c10d3aa 100644 --- a/config/services.php +++ b/config/services.php @@ -48,12 +48,14 @@ // Allow overriding the PUG REST path if needed 'pug_rest_path' => env('PUBCHEM_PUG_PATH', '/rest/pug'), ], - 'common_chemistry' => [ - 'base_url' => env('COMMON_CHEMISTRY_URL', 'https://commonchemistry.cas.org'), - 'api_path' => env('COMMON_CHEMISTRY_API_PATH', '/api'), - ], + 'chemistry_standardize' => [ - 'url' => env('CHEMISTRY_STANDARDIZE_URL', 'https://api.cheminf.studio/latest/chem/standardize'), + 'url' => env('CHEMISTRY_STANDARDIZE_URL', 'https://api.naturalproducts.net/latest/chem/standardize'), ], + 'cas' => [ + 'provider' => env('CAS_PROVIDER', 'CAS_CommonChemistry'), + 'api_token' => env('CAS_API_TOKEN'), + 'base_url' => env('COMMON_CHEMISTRY_URL', 'https://commonchemistry.cas.org/api'), + ], ]; diff --git a/database/factories/FileSystemObjectFactory.php b/database/factories/FileSystemObjectFactory.php new file mode 100644 index 00000000..38216f21 --- /dev/null +++ b/database/factories/FileSystemObjectFactory.php @@ -0,0 +1,141 @@ + + */ +class FileSystemObjectFactory extends Factory +{ + /** + * Define the model's default state. + */ + public function definition(): array + { + return [ + 'name' => $this->faker->word().'.'.$this->faker->fileExtension(), + 'uuid' => Str::uuid(), + 'slug' => $this->faker->slug(), + 'description' => $this->faker->sentence(), + 'relative_url' => '/'.$this->faker->slug().'/'.$this->faker->word(), + 'path' => $this->faker->filePath(), + 'type' => $this->faker->randomElement(['file', 'directory']), // Changed from 'folder' to 'directory' + 'key' => Str::uuid(), + 'is_public' => false, + 'is_deleted' => false, + 'is_archived' => false, + 'is_original' => true, + 'is_verified' => false, + 'is_processed' => false, + 'is_root' => false, + 'sort_order' => $this->faker->numberBetween(1, 100), + 'level' => $this->faker->numberBetween(0, 3), + 'has_children' => false, + 'file_size' => $this->faker->numberBetween(1024, 1048576), // 1KB to 1MB + 'integrity_status' => $this->faker->randomElement(['pending', 'verified', 'failed']), + 'status' => $this->faker->randomElement(['present', 'missing']), + 'checksum_md5' => md5($this->faker->text()), + 'checksum_sha256' => hash('sha256', $this->faker->text()), + 'checksum_algorithm' => 'sha256', + ]; + } + + /** + * Configure the factory for a file type. + */ + public function file(): static + { + return $this->state(fn (array $attributes) => [ + 'type' => 'file', + 'has_children' => false, + ]); + } + + /** + * Configure the factory for a directory type. + */ + public function directory(): static + { + return $this->state(fn (array $attributes) => [ + 'type' => 'directory', + 'has_children' => true, + ]); + } + + /** + * Configure the factory for a missing status. + */ + public function missing(): static + { + return $this->state(fn (array $attributes) => [ + 'status' => 'missing', + ]); + } + + /** + * Configure the factory for a complete status. + */ + public function complete(): static + { + return $this->state(fn (array $attributes) => [ + 'status' => 'complete', + ]); + } + + /** + * Configure the factory for root level files. + */ + public function rootLevel(): static + { + return $this->state(fn (array $attributes) => [ + 'level' => 0, + 'parent_id' => null, + ]); + } + + /** + * Configure the factory for child files. + */ + public function childLevel(int $level = 1): static + { + return $this->state(fn (array $attributes) => [ + 'level' => $level, + ]); + } + + /** + * Configure the factory to belong to a draft. + */ + public function forDraft(Draft $draft): static + { + return $this->state(fn (array $attributes) => [ + 'draft_id' => $draft->id, + ]); + } + + /** + * Configure the factory to belong to a project. + */ + public function forProject(Project $project): static + { + return $this->state(fn (array $attributes) => [ + 'project_id' => $project->id, + ]); + } + + /** + * Configure the factory to belong to a study. + */ + public function forStudy(Study $study): static + { + return $this->state(fn (array $attributes) => [ + 'study_id' => $study->id, + ]); + } +} diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php index abfd28b9..6b0545ac 100644 --- a/database/factories/LicenseFactory.php +++ b/database/factories/LicenseFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\License; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; @@ -10,21 +11,28 @@ */ class LicenseFactory extends Factory { + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = License::class; + /** * Define the model's default state. */ public function definition(): array { - $title = $this->faker->sentence($nbWords = 4); + $title = fake()->sentence(4); $slug = Str::slug($title, '-'); return [ 'title' => $title, 'slug' => $slug, 'spdx_id' => Str::random(), - 'url' => $this->faker->url(), - 'description' => $this->faker->text(), - 'body' => $this->faker->text(), + 'url' => fake()->url(), + 'description' => fake()->text(), + 'body' => fake()->text(), 'category' => Str::random(40), ]; } diff --git a/database/factories/LinkedSocialAccountFactory.php b/database/factories/LinkedSocialAccountFactory.php new file mode 100644 index 00000000..04e5b8c1 --- /dev/null +++ b/database/factories/LinkedSocialAccountFactory.php @@ -0,0 +1,54 @@ + User::factory(), + 'provider_id' => $this->faker->unique()->numerify('########'), + 'provider_name' => $this->faker->randomElement(['github', 'google', 'orcid']), + ]; + } + + /** + * Indicate that the linked account is for GitHub. + */ + public function github(): static + { + return $this->state(fn (array $attributes) => [ + 'provider_name' => 'github', + ]); + } + + /** + * Indicate that the linked account is for Google. + */ + public function google(): static + { + return $this->state(fn (array $attributes) => [ + 'provider_name' => 'google', + ]); + } + + /** + * Indicate that the linked account is for ORCID. + */ + public function orcid(): static + { + return $this->state(fn (array $attributes) => [ + 'provider_name' => 'orcid', + ]); + } +} diff --git a/database/factories/MoleculeFactory.php b/database/factories/MoleculeFactory.php index b988ef89..b6c654a3 100644 --- a/database/factories/MoleculeFactory.php +++ b/database/factories/MoleculeFactory.php @@ -12,74 +12,55 @@ class MoleculeFactory extends Factory */ public function definition(): array { - $cid = rand(1000, 9999); - echo $cid; - $pubchemRecordLink = 'https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/'.$cid.'/record/JSON'; - $json = file_get_contents($pubchemRecordLink); - $data = json_decode($json, true)['PC_Compounds'][0]['props']; + // Generate fake molecule data instead of making API calls + // This prevents external API dependencies and rate limiting issues - $output = []; - $labels = [ - 'InChI' => 'standard_inchi', - 'InChIKey' => 'inchi_key', - 'Molecular Formula' => 'molecular_formula', - ]; - - foreach ($data as $key => $value) { - $pubchemLabel = $data[$key]['urn']['label']; - - foreach ($labels as $label => $column) { - if ($pubchemLabel == $label) { - $val = $data[$key]['value']['sval']; - $output[$column] = $val; - } - } + // Generate unique molecular formulas using random carbon/hydrogen/oxygen counts + $c = $this->faker->numberBetween(6, 25); + $h = $this->faker->numberBetween(8, 50); + $o = $this->faker->numberBetween(0, 10); + $molecularFormula = "C{$c}H{$h}"; + if ($o > 0) { + $molecularFormula .= "O{$o}"; } - return - [ + // Generate unique InChI and InChI key using random components + $uniqueId = $this->faker->uuid(); + $hashPart = substr(md5($uniqueId), 0, 10); + $standardInchi = "InChI=1S/{$molecularFormula}/c{$hashPart}/h{$this->faker->randomNumber(5)}"; + $inchiKey = strtoupper(substr(md5($standardInchi), 0, 14)).'-'.strtoupper(substr(md5($uniqueId), 0, 10)).'-N'; + + return [ 'cas' => null, - 'molecular_formula' => $output['molecular_formula'], - 'molecular_weight' => null, + 'molecular_formula' => $molecularFormula, + 'molecular_weight' => $this->faker->randomFloat(2, 100, 500), 'smiles' => null, 'absolute_smiles' => null, 'canonical_smiles' => null, 'inchi' => null, - 'standard_inchi' => $output['standard_inchi'], - 'inchi_key' => $output['inchi_key'], + 'standard_inchi' => $standardInchi, + 'inchi_key' => $inchiKey, 'standard_inchi_key' => null, - 'fp0' => null, - 'fp1' => null, - 'fp2' => null, - 'fp3' => null, - 'fp4' => null, - 'fp5' => null, - 'fp6' => null, - 'fp7' => null, - 'fp8' => null, - 'fp9' => null, - 'fp10' => null, - 'fp11' => null, - 'fp12' => null, - 'fp13' => null, - 'fp14' => null, - 'fp15' => null, - 'DBE' => null, - 'SSSR' => null, - 'SAR' => null, - 'COMMENT' => null, 'sdf' => null, - 'MULTIPLICITY_0' => null, - 'MULTIPLICITY_1' => null, - 'MULTIPLICITY_2' => null, - 'MULTIPLICITY_3' => null, - 'VIEWS' => null, 'DOI' => null, 'created_at' => Carbon::now()->timestamp, 'updated_at' => Carbon::now()->timestamp, 'doi' => null, 'datacite_schema' => null, - 'identifier' => null, + 'identifier' => $this->faker->unique()->numberBetween(1, 999999), // bigint identifier for searches + 'name' => $this->faker->words(2, true), + 'name_trust_level' => 0, + 'annotation_level' => 0, + 'synonyms' => null, + 'iupac_name' => null, + '2d' => null, + '3d' => null, + 'structural_comments' => null, + 'status' => 'APPROVED', + 'active' => true, + 'has_stereo' => false, + 'has_variants' => false, + 'variants_count' => 0, ]; } } diff --git a/database/factories/NMRiumFactory.php b/database/factories/NMRiumFactory.php index 714f3637..1f2fd53b 100644 --- a/database/factories/NMRiumFactory.php +++ b/database/factories/NMRiumFactory.php @@ -16,7 +16,30 @@ public function definition(): array { return [ 'nmrium_info' => '{}', - 'dataset_id' => 1, + 'nmriumable_id' => 1, + 'nmriumable_type' => \App\Models\Dataset::class, ]; } + + /** + * Make this NMRium belong to a Dataset + */ + public function forDataset($dataset = null): static + { + return $this->state(fn (array $attributes) => [ + 'nmriumable_id' => $dataset?->id ?? \App\Models\Dataset::factory(), + 'nmriumable_type' => \App\Models\Dataset::class, + ]); + } + + /** + * Make this NMRium belong to a Study + */ + public function forStudy($study = null): static + { + return $this->state(fn (array $attributes) => [ + 'nmriumable_id' => $study?->id ?? \App\Models\Study::factory(), + 'nmriumable_type' => \App\Models\Study::class, + ]); + } } diff --git a/database/factories/ProjectInvitationFactory.php b/database/factories/ProjectInvitationFactory.php new file mode 100644 index 00000000..fac1d05d --- /dev/null +++ b/database/factories/ProjectInvitationFactory.php @@ -0,0 +1,59 @@ + + */ +class ProjectInvitationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'project_id' => Project::factory(), + 'email' => fake()->unique()->safeEmail(), + 'role' => fake()->randomElement(['viewer', 'collaborator']), + 'message' => fake()->optional()->sentence(), + 'invited_by' => User::factory(), + ]; + } + + /** + * Indicate that the invitation is for a collaborator role. + */ + public function collaborator(): static + { + return $this->state(fn (array $attributes) => [ + 'role' => 'collaborator', + ]); + } + + /** + * Indicate that the invitation is for a viewer role. + */ + public function viewer(): static + { + return $this->state(fn (array $attributes) => [ + 'role' => 'viewer', + ]); + } + + /** + * Indicate that the invitation has expired. + */ + public function expired(): static + { + return $this->state(fn (array $attributes) => [ + 'created_at' => now()->subDays(8), // Assuming 7-day expiry + ]); + } +} diff --git a/database/factories/StudyFactory.php b/database/factories/StudyFactory.php index d6beca72..ee20b8d5 100644 --- a/database/factories/StudyFactory.php +++ b/database/factories/StudyFactory.php @@ -2,7 +2,6 @@ namespace Database\Factories; -use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; @@ -42,8 +41,6 @@ public function definition(): array 'fs_id' => 1, 'release_date' => null, 'study_photo_path' => null, // todo: Adjust when studies images field is provided in nmrXiv - 'created_at' => Carbon::now()->timestamp, - 'updated_at' => Carbon::now()->timestamp, 'doi' => null, 'datacite_schema' => null, 'identifier' => null, diff --git a/database/factories/StudyInvitationFactory.php b/database/factories/StudyInvitationFactory.php new file mode 100644 index 00000000..2a7be6e7 --- /dev/null +++ b/database/factories/StudyInvitationFactory.php @@ -0,0 +1,77 @@ + Study::factory(), + 'email' => $this->faker->unique()->safeEmail(), + 'role' => $this->faker->randomElement(['viewer', 'collaborator', 'reviewer']), + 'message' => $this->faker->optional()->sentence(), + 'invited_by' => $this->faker->optional()->safeEmail(), + ]; + } + + /** + * Factory state for viewer role + */ + public function viewer(): self + { + return $this->state(fn (array $attributes) => [ + 'role' => 'viewer', + ]); + } + + /** + * Factory state for collaborator role + */ + public function collaborator(): self + { + return $this->state(fn (array $attributes) => [ + 'role' => 'collaborator', + ]); + } + + /** + * Factory state for reviewer role + */ + public function reviewer(): self + { + return $this->state(fn (array $attributes) => [ + 'role' => 'reviewer', + ]); + } + + /** + * Factory state with a specific inviter + */ + public function invitedBy(User $user): self + { + return $this->state(fn (array $attributes) => [ + 'invited_by' => $user->email, + ]); + } + + /** + * Factory state for a specific study + */ + public function forStudy(Study $study): self + { + return $this->state(fn (array $attributes) => [ + 'study_id' => $study->id, + ]); + } +} diff --git a/database/factories/TeamInvitationFactory.php b/database/factories/TeamInvitationFactory.php new file mode 100644 index 00000000..b76b3343 --- /dev/null +++ b/database/factories/TeamInvitationFactory.php @@ -0,0 +1,88 @@ + + */ +class TeamInvitationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'team_id' => Team::factory(), + 'email' => fake()->unique()->safeEmail(), + 'role' => fake()->randomElement(['admin', 'editor']), + 'invited_by' => User::factory(), + ]; + } + + /** + * Indicate that the invitation is for an admin role. + */ + public function admin(): static + { + return $this->state(fn (array $attributes) => [ + 'role' => 'admin', + ]); + } + + /** + * Indicate that the invitation is for an editor role. + */ + public function editor(): static + { + return $this->state(fn (array $attributes) => [ + 'role' => 'editor', + ]); + } + + /** + * Indicate that the invitation is for an owner role. + */ + public function owner(): static + { + return $this->state(fn (array $attributes) => [ + 'role' => 'owner', + ]); + } + + /** + * Set a specific email for the invitation. + */ + public function forEmail(string $email): static + { + return $this->state(fn (array $attributes) => [ + 'email' => $email, + ]); + } + + /** + * Set a specific team for the invitation. + */ + public function forTeam(Team $team): static + { + return $this->state(fn (array $attributes) => [ + 'team_id' => $team->id, + ]); + } + + /** + * Set a specific inviter for the invitation. + */ + public function invitedBy(User $user): static + { + return $this->state(fn (array $attributes) => [ + 'invited_by' => $user->id, + ]); + } +} diff --git a/database/factories/TickerFactory.php b/database/factories/TickerFactory.php new file mode 100644 index 00000000..3676b451 --- /dev/null +++ b/database/factories/TickerFactory.php @@ -0,0 +1,81 @@ + + */ +class TickerFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = Ticker::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'type' => $this->faker->randomElement(['sample', 'molecule', 'dataset']), + 'index' => $this->faker->numberBetween(1, 1000), + 'meta' => null, + ]; + } + + /** + * Create a ticker for samples + */ + public function sample(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'type' => 'sample', + ]; + }); + } + + /** + * Create a ticker for molecules + */ + public function molecule(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'type' => 'molecule', + ]; + }); + } + + /** + * Create a ticker for datasets + */ + public function dataset(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'type' => 'dataset', + ]; + }); + } + + /** + * Set a specific index value + */ + public function withIndex(int $index): Factory + { + return $this->state(function (array $attributes) use ($index) { + return [ + 'index' => $index, + ]; + }); + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index da47b941..66c62b21 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -21,7 +21,7 @@ public function definition(): array 'last_name' => $this->faker->lastName(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), - 'username' => $this->faker->userName(), + 'username' => $this->faker->unique()->userName(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; diff --git a/database/factories/ValidationFactory.php b/database/factories/ValidationFactory.php new file mode 100644 index 00000000..aeefb801 --- /dev/null +++ b/database/factories/ValidationFactory.php @@ -0,0 +1,93 @@ + + */ +class ValidationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'report' => [ + 'project' => [ + 'status' => fake()->boolean(), + 'title' => fake()->boolean(), + 'description' => fake()->boolean(), + 'authors' => fake()->boolean(), + 'affiliation' => fake()->boolean(), + 'license' => fake()->boolean(), + 'keywords' => fake()->boolean(), + 'studies' => [ + ['status' => fake()->boolean()], + ['status' => fake()->boolean()], + ], + ], + 'missing' => [], + 'errors' => [], + 'version' => 1, + ], + ]; + } + + /** + * Indicate that the validation has passed. + */ + public function passed(): static + { + return $this->state(fn (array $attributes) => [ + 'report' => [ + 'project' => [ + 'status' => true, + 'title' => true, + 'description' => true, + 'authors' => true, + 'affiliation' => true, + 'license' => true, + 'keywords' => true, + 'studies' => [ + ['status' => true], + ], + ], + 'missing' => [], + 'errors' => [], + 'version' => 1, + ], + ]); + } + + /** + * Indicate that the validation has failed. + */ + public function failed(): static + { + return $this->state(fn (array $attributes) => [ + 'report' => [ + 'project' => [ + 'status' => false, + 'title' => false, + 'description' => false, + 'authors' => false, + 'affiliation' => false, + 'license' => false, + 'keywords' => false, + 'studies' => [ + ['status' => false], + ['status' => false], + ], + ], + 'missing' => ['title', 'description', 'authors'], + 'errors' => ['Project validation failed'], + 'version' => 1, + ], + ]); + } +} diff --git a/database/migrations/2025_12_11_115948_add_ror_id_to_users_table.php b/database/migrations/2025_12_11_115948_add_ror_id_to_users_table.php new file mode 100644 index 00000000..0b3046f9 --- /dev/null +++ b/database/migrations/2025_12_11_115948_add_ror_id_to_users_table.php @@ -0,0 +1,28 @@ +string('ror_id')->nullable()->after('affiliation'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('ror_id'); + }); + } +}; diff --git a/database/migrations/2025_12_15_105635_change_affiliation_to_text_in_users_table.php b/database/migrations/2025_12_15_105635_change_affiliation_to_text_in_users_table.php new file mode 100644 index 00000000..19011106 --- /dev/null +++ b/database/migrations/2025_12_15_105635_change_affiliation_to_text_in_users_table.php @@ -0,0 +1,28 @@ +text('affiliation')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->string('affiliation')->nullable()->change(); + }); + } +}; diff --git a/database/migrations/2025_12_15_115641_add_ror_id_to_authors_table.php b/database/migrations/2025_12_15_115641_add_ror_id_to_authors_table.php new file mode 100644 index 00000000..29b667b2 --- /dev/null +++ b/database/migrations/2025_12_15_115641_add_ror_id_to_authors_table.php @@ -0,0 +1,28 @@ +string('ror_id')->nullable()->after('affiliation'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('authors', function (Blueprint $table) { + $table->dropColumn('ror_id'); + }); + } +}; diff --git a/database/migrations/2026_02_09_091118_create_author_study_table.php b/database/migrations/2026_02_09_091118_create_author_study_table.php new file mode 100644 index 00000000..84efddf6 --- /dev/null +++ b/database/migrations/2026_02_09_091118_create_author_study_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedBigInteger('author_id'); + $table->unsignedBigInteger('study_id'); + $table->string('contributor_type')->nullable(); + $table->smallInteger('sort_order')->nullable(); + $table->timestamps(); + + $table->foreign('author_id')->references('id')->on('authors')->onDelete('cascade'); + $table->foreign('study_id')->references('id')->on('studies')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('author_study'); + } +}; diff --git a/database/migrations/2026_02_19_120638_add_metadata_extraction_bagit_generation_columns_to_studies_table.php b/database/migrations/2026_02_19_120638_add_metadata_extraction_bagit_generation_columns_to_studies_table.php new file mode 100644 index 00000000..d9f1e450 --- /dev/null +++ b/database/migrations/2026_02_19_120638_add_metadata_extraction_bagit_generation_columns_to_studies_table.php @@ -0,0 +1,29 @@ +string('metadata_bagit_generation_status')->nullable()->after('is_public'); + $table->json('metadata_bagit_generation_logs')->nullable()->after('metadata_bagit_generation_status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('studies', function (Blueprint $table) { + $table->dropColumn(['metadata_bagit_generation_status', 'metadata_bagit_generation_logs']); + }); + } +}; diff --git a/deployment/Dockerfile b/deployment/Dockerfile index 7c5222c6..013fb1fa 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,4 +1,4 @@ -FROM dunglas/frankenphp:1.7.0-alpine +FROM dunglas/frankenphp:1-php8.4-alpine # Setup proxy settings using build arguments ARG HTTP_PROXY @@ -44,7 +44,7 @@ COPY deployment/php.production.ini /usr/local/etc/php/conf.d/production.ini COPY composer.json composer.lock package*.json ./ # Install PHP dependencies -RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress --no-scripts +RUN composer install --optimize-autoloader --no-interaction --no-progress --no-scripts # Install Node dependencies RUN npm ci --production=false @@ -77,6 +77,10 @@ COPY deployment/Caddyfile /etc/caddy/Caddyfile RUN php artisan l5-swagger:generate +# Add healthcheck - use Laravel health endpoint for broader compatibility +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl -f http://localhost/up || exit 1 + # Expose port EXPOSE 80 443 diff --git a/deployment/Dockerfile.worker b/deployment/Dockerfile.worker index fd15d46f..2d434510 100644 --- a/deployment/Dockerfile.worker +++ b/deployment/Dockerfile.worker @@ -1,4 +1,4 @@ -FROM php:8.3-alpine +FROM dunglas/frankenphp:1-php8.4-alpine # Setup proxy settings using build arguments ARG HTTP_PROXY @@ -23,8 +23,7 @@ RUN /sbin/apk add --no-cache \ oniguruma-dev \ postgresql-dev \ zlib-dev \ - supervisor \ - $PHPIZE_DEPS + supervisor # Install PostgreSQL client RUN /sbin/apk --update add postgresql17-client --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main @@ -32,31 +31,12 @@ RUN /sbin/apk --update add postgresql17-client --repository=https://dl-cdn.alpin # Install PHP extensions RUN docker-php-ext-install pdo pdo_pgsql mbstring exif pcntl bcmath gd intl zip -# Install APCu and Redis PHP extensions from source -RUN cd /tmp && \ - # Download APCu from GitHub - curl -L -o apcu.tar.gz https://github.com/krakjoe/apcu/archive/refs/tags/v5.1.22.tar.gz && \ - mkdir -p /tmp/apcu && \ - tar -xf apcu.tar.gz -C /tmp/apcu --strip-components=1 && \ - cd /tmp/apcu && \ - phpize && \ - ./configure && \ - make && \ - make install && \ - # Download Redis from GitHub - cd /tmp && \ - curl -L -o redis.tar.gz https://github.com/phpredis/phpredis/archive/refs/tags/5.3.7.tar.gz && \ - mkdir -p /tmp/redis && \ - tar -xf redis.tar.gz -C /tmp/redis --strip-components=1 && \ - cd /tmp/redis && \ - phpize && \ - ./configure && \ - make && \ - make install && \ - # Enable extensions +# Install APCu and Redis via PECL with build dependencies added and removed in same layer +RUN /sbin/apk add --no-cache --virtual .build-deps autoconf gcc g++ make && \ + pecl install apcu redis && \ docker-php-ext-enable apcu redis && \ - # Clean up - rm -rf /tmp/apcu /tmp/redis /tmp/apcu.tar.gz /tmp/redis.tar.gz + /sbin/apk del .build-deps && \ + rm -rf /tmp/pear # Configure PHP COPY deployment/php.production.ini /usr/local/etc/php/conf.d/production.ini @@ -86,5 +66,9 @@ COPY deployment/supervisord.worker.conf /etc/supervisor/conf.d/supervisord.conf # Create required directories for Supervisor RUN mkdir -p /var/log/supervisor /run/supervisor +# Add healthcheck - verify Horizon process is running +HEALTHCHECK --interval=10s --timeout=3s --start-period=30s --retries=3 \ + CMD pgrep -f "artisan horizon" || exit 1 + # Start Supervisord CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 286dc455..55538546 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ services: laravel.test: build: - context: ./docker/8.3 + context: ./docker/8.4 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' - image: sail-8.3/app + image: sail-8.4/app extra_hosts: - 'host.docker.internal:host-gateway' ports: diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile new file mode 100644 index 00000000..f647912f --- /dev/null +++ b/docker/8.4/Dockerfile @@ -0,0 +1,64 @@ +FROM ubuntu:22.04 + +LABEL maintainer="Taylor Otwell" + +ARG WWWGROUP +ARG NODE_VERSION=20 +ARG POSTGRES_VERSION=15 + +WORKDIR /var/www/html + +ENV DEBIAN_FRONTEND noninteractive +ENV TZ=UTC +ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN apt-get update \ + && mkdir -p /etc/apt/keyrings \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch \ + && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ + && apt-get update \ + && apt-get install -y php8.4-cli php8.4-dev \ + php8.4-pgsql php8.4-sqlite3 php8.4-gd \ + php8.4-curl \ + php8.4-imap php8.4-mysql php8.4-mbstring \ + php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \ + php8.4-intl php8.4-readline \ + php8.4-ldap \ + php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \ + php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \ + && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && npm install -g npm \ + && npm install -g pnpm \ + && npm install -g bun \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ + && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update \ + && apt-get install -y yarn \ + && apt-get install -y mysql-client \ + && apt-get install -y postgresql-client-$POSTGRES_VERSION \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4 + +RUN groupadd --force -g $WWWGROUP sail +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail + +COPY start-container /usr/local/bin/start-container +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini +RUN chmod +x /usr/local/bin/start-container + +EXPOSE 8000 + +ENTRYPOINT ["start-container"] diff --git a/docker/8.4/php.ini b/docker/8.4/php.ini new file mode 100644 index 00000000..26dbdf6e --- /dev/null +++ b/docker/8.4/php.ini @@ -0,0 +1,10 @@ +[PHP] +post_max_size = 2G +upload_max_filesize = 2G +memory_limit = 2G +max_execution_time = 259200 +max_input_time = 259200 +variables_order = EGPCS + +[opcache] +opcache.enable_cli=1 diff --git a/docker/8.4/start-container b/docker/8.4/start-container new file mode 100644 index 00000000..b8643990 --- /dev/null +++ b/docker/8.4/start-container @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -d /.composer ]; then + mkdir /.composer +fi + +chmod -R ugo+rw /.composer + +if [ $# -gt 0 ]; then + exec gosu $WWWUSER "$@" +else + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf +fi diff --git a/docker/8.4/supervisord.conf b/docker/8.4/supervisord.conf new file mode 100644 index 00000000..26ccc0a5 --- /dev/null +++ b/docker/8.4/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php] +command=%(ENV_SUPERVISOR_PHP_COMMAND)s +user=sail +environment=LARAVEL_SAIL="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docs/developer-guides/csp-summary.md b/docs/developer-guides/csp-summary.md new file mode 100644 index 00000000..aecc93a4 --- /dev/null +++ b/docs/developer-guides/csp-summary.md @@ -0,0 +1,113 @@ +# nmrXiv Content Security Policy (CSP) summary + +This guide summarizes the CSP implementation in nmrXiv, the key changes we made, and how to work with it in development and production. + +## What changed + +- Introduced a custom CSP preset: `App/Support/Csp/Policies/NmrxivPolicy.php` (Spatie preset). +- Configured CSP in `config/csp.php` with support for both enforcement and report-only modes. +- Added a violation reporting endpoint (POST) and a viewer (GET) at `/csp-violation-report`. +- Updated templates to cooperate with CSP (nonces in production, permissive inline in development). +- Fixed third-party allowances (Matomo, Bunny Fonts) and dev tooling (Vite/HMR, IPv6 `localhost`). + +## Files involved + +- `app/Support/Csp/Policies/NmrxivPolicy.php` + - Central, environment-aware policy. + - Production: nonces are used; no `unsafe-inline`/`unsafe-eval`. + - Development/local: nonces are disabled; `unsafe-inline`/`unsafe-eval` permitted for DX and Vite HMR. + - Explicitly allows required third-party and dev sources (see below). + +- `config/csp.php` + - Switch between enforcement and report-only by moving the policy class between `presets` and `report_only_presets`. + - Controls `enabled`, `report_uri`, nonce generator, and whether to enable during hot reloading. + +- `routes/web.php` + - `POST /csp-violation-report` → receives reports (throttled). + - `GET /csp-violation-report` → view latest parsed violations. + +- `app/Http/Controllers/CspViolationController.php` + - Logs violation payloads and returns recent entries for inspection. + +- `resources/views/app.blade.php` + - Uses `@cspNonce` where needed in production (e.g., analytics). + - In development, policy disables nonces to avoid the browser ignoring `unsafe-inline`. + +## Effective directives (overview) + +Always applied (with environment-specific additions): +- `base-uri 'self'` +- `default-src 'self'` +- `object-src 'none'` +- `frame-src 'self'` +- `form-action 'self'` +- `img-src 'self' data: blob:` (+ localhost allowances in dev) +- `media-src 'self' blob:` +- `font-src data: https://fonts.bunny.net` +- `style-src 'self' data: https://fonts.bunny.net` (behavior differs by env) +- `script-src 'self' https://matomo.nfdi4chem.de` (behavior differs by env) +- `connect-src 'self' https://matomo.nfdi4chem.de https://fonts.bunny.net` (+ dev sockets/hosts) +- `report-uri /csp-violation-report` + +### Production (hardened) +- Nonces enabled (generated by `Spatie\Csp\Nonce\RandomString`). + - `script-src 'self' 'nonce-…' …` + - `style-src 'self' 'nonce-…' …` +- No `unsafe-inline` and no `unsafe-eval`. +- Third‑party: `https://matomo.nfdi4chem.de`, `https://fonts.bunny.net`. + +### Development/local (DX-friendly) +- Nonces disabled to allow inline execution where needed by tooling. +- Allows: + - `script-src … 'unsafe-inline' 'unsafe-eval'` + - `style-src … 'unsafe-inline'` +- Vite/HMR and dev connections (IPv4, named, and IPv6): + - Scripts: `http://localhost:5173`, `http://127.0.0.1:5173`, `http://[::1]:5173` (also 3000, 8000 variants) + - WebSockets: `ws://localhost:5173`, `ws://127.0.0.1:5173`, `ws://[::1]:5173` (also 3000 variants) + - HTTP connects for the same hosts/ports. +- Images additionally allow `http(s)://localhost:*`. + +## Third‑party notes +- Matomo: use explicit `https://matomo.nfdi4chem.de` (protocol‑relative sources like `//…` are invalid in CSP). +- Fonts: `https://fonts.bunny.net` allowed for `font-src` and `style-src`. + +## Violation reporting +- POST reports to: `/csp-violation-report` (throttled; can be relaxed locally if noisy). +- GET: `/csp-violation-report` returns a recent, parsed list for quick inspection. + +## Switching modes +- Enforcement: put `App\Support\Csp\Policies\NmrxivPolicy::class` into `presets` and remove it from `report_only_presets` in `config/csp.php`. +- Report‑only: place it in `report_only_presets` and clear from `presets`. +- Apply changes: + +```bash +php artisan config:clear +``` + +## Verifying the header + +```bash +curl -I http://localhost/ | grep -i "content-security-policy" +``` +- Development: expect `unsafe-inline`/`unsafe-eval` and Vite endpoints (including IPv6 `[::1]`). +- Production: expect `nonce-…` values; no `unsafe-inline`/`unsafe-eval`. + +## Troubleshooting +- “unsafe-inline is ignored when a hash or nonce is present” + - Expected: browsers ignore `unsafe-inline` when nonces/hashes are present. We avoid this in dev by disabling nonces there. +- “Refused to load script http://[::1]:5173/…” + - Ensure IPv6 dev hosts are included in both `script-src` and `connect-src` (policy does this). +- Many 429s to `/csp-violation-report` + - Temporarily relax throttle for development or disable reporting while testing. +- `browser-sync-client.js net::ERR_CONNECTION_REFUSED` + - Not CSP. Start BrowserSync or remove the script include locally. + +## Edit locations +- Policy: `app/Support/Csp/Policies/NmrxivPolicy.php` +- Config: `config/csp.php` +- Routes: `routes/web.php` +- Violation controller: `app/Http/Controllers/CspViolationController.php` +- App shell: `resources/views/app.blade.php` + +— +If you must allow new third‑party origins, add the narrowest possible directive for the exact origin and protocol. Avoid `unsafe-*` in production; prefer nonces and explicit source lists. diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 00000000..d458da13 --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'These credentials do not match our records.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 00000000..5704d616 --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,23 @@ + 'Your password has been reset.', + 'sent' => 'If an account exists with this email, you will receive a password reset link.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => 'If an account exists with this email, you will receive a password reset link.', + +]; diff --git a/package-lock.json b/package-lock.json index 5632976a..7f506481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "nmrxiv", "dependencies": { "@headlessui/vue": "^1.6.2", "@heroicons/vue": "^2.0.11", @@ -27,6 +26,7 @@ "openchemlib": "^7.4.3", "pluralize": "^8.0.0", "popper.js": "^1.16.1", + "qs": "^6.14.1", "vue-instantsearch": "^4.3.3", "vue-simple-context-menu": "^4.0.4", "vue3-clipboard": "^1.0.0", @@ -46,8 +46,10 @@ "eslint": "^8.21.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-vue": "^9.3.0", + "husky": "^9.1.7", "laravel-vapor": "^0.7.1", "laravel-vite-plugin": "^1.2.0", + "lint-staged": "^16.2.7", "lodash": "^4.17.19", "postcss": "^8.4.19", "postcss-import": "^16.1.0", @@ -323,9 +325,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -987,9 +989,9 @@ "license": "BSD-3-Clause" }, "node_modules/@iconify-json/simple-icons": { - "version": "1.2.52", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.52.tgz", - "integrity": "sha512-c41YOMzBhl3hp58WJLxT+Qq3UhBd8GZAMkbS8ddlCuIGLW0COGe2YSfOA2+poA8/bxLhUQODRNjAy3KhiAOtzA==", + "version": "1.2.70", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.70.tgz", + "integrity": "sha512-CYNRCgN6nBTjN4dNkrBCjHXNR2e4hQihdsZUs/afUNFOWLSYjfihca4EFN05rRvDk4Xoy2n8tym6IxBZmcn+Qg==", "dev": true, "license": "CC0-1.0", "dependencies": { @@ -1911,23 +1913,23 @@ } }, "node_modules/@vue/devtools-api": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", - "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.7.7" + "@vue/devtools-kit": "^7.7.9" } }, "node_modules/@vue/devtools-kit": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", - "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.7.7", + "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", @@ -1937,9 +1939,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", - "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", "dev": true, "license": "MIT", "dependencies": { @@ -2544,6 +2546,22 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2645,13 +2663,13 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2677,6 +2695,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2700,9 +2728,9 @@ } }, "node_modules/birpc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", - "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", "dev": true, "license": "MIT", "funding": { @@ -2740,9 +2768,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2760,10 +2788,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2829,9 +2858,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true, "funding": [ { @@ -2959,6 +2988,85 @@ "node": ">=6.0" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/click-outside-vue3": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/click-outside-vue3/-/click-outside-vue3-4.0.1.tgz", @@ -3012,6 +3120,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3078,16 +3193,16 @@ } }, "node_modules/copy-anything": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", "dev": true, "license": "MIT", "dependencies": { - "is-what": "^4.1.8" + "is-what": "^5.2.0" }, "engines": { - "node": ">=12.13" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/mesqueeb" @@ -3303,9 +3418,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.185", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.185.tgz", - "integrity": "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true, "license": "ISC" }, @@ -3333,15 +3448,15 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -3359,6 +3474,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3378,9 +3506,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT", "peer": true @@ -3684,6 +3812,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3745,9 +3880,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -3836,19 +3971,19 @@ "license": "ISC" }, "node_modules/focus-trap": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", - "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", "dev": true, "license": "MIT", "dependencies": { - "tabbable": "^6.2.0" + "tabbable": "^6.4.0" } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -3882,9 +4017,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3951,6 +4086,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4220,6 +4368,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4308,18 +4472,6 @@ "algoliasearch": ">= 3.1 < 6" } }, - "node_modules/instantsearch.js/node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4418,13 +4570,13 @@ } }, "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.13" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/mesqueeb" @@ -4614,15 +4766,157 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "peer": true, "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -4657,16 +4951,16 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash.castarray": { @@ -4703,6 +4997,111 @@ "dev": true, "license": "MIT" }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -4928,6 +5327,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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", @@ -5001,6 +5413,19 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -5055,9 +5480,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -5154,6 +5579,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/oniguruma-to-es": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", @@ -5325,6 +5766,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -5487,9 +5941,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.26.9", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", - "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", "license": "MIT", "funding": { "type": "opencollective", @@ -5550,9 +6004,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5617,9 +6071,9 @@ } }, "node_modules/regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", - "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", "dev": true, "license": "MIT", "dependencies": { @@ -5694,6 +6148,23 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5824,9 +6295,9 @@ "peer": true }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "peer": true, @@ -6055,6 +6526,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sortablejs": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", @@ -6114,6 +6631,16 @@ "node": ">=0.10.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6263,13 +6790,13 @@ } }, "node_modules/superjson": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", "dev": true, "license": "MIT", "dependencies": { - "copy-anything": "^3.0.2" + "copy-anything": "^4" }, "engines": { "node": ">=16" @@ -6304,9 +6831,9 @@ } }, "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "dev": true, "license": "MIT" }, @@ -6423,14 +6950,18 @@ } }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "peer": true, "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { @@ -6454,9 +6985,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "license": "MIT", "peer": true, @@ -6669,9 +7200,9 @@ "peer": true }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6711,9 +7242,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "dev": true, "license": "MIT", "dependencies": { @@ -6727,9 +7258,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6742,9 +7273,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -7783,9 +8314,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "peer": true, @@ -7804,9 +8335,9 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.100.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz", - "integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.1.tgz", + "integrity": "sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==", "dev": true, "license": "MIT", "peer": true, @@ -7819,22 +8350,22 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.2", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { @@ -7989,15 +8520,18 @@ } }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 448442ef..52d97942 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,17 @@ "format": "prettier resources/js --write", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" + "docs:preview": "vitepress preview docs", + "prepare": "husky" + }, + "lint-staged": { + "*.php": [ + "vendor/bin/pint" + ], + "*.{js,vue}": [ + "eslint --fix", + "prettier --write" + ] }, "devDependencies": { "@tailwindcss/forms": "^0.5.3", @@ -20,8 +30,10 @@ "eslint": "^8.21.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-vue": "^9.3.0", + "husky": "^9.1.7", "laravel-vapor": "^0.7.1", "laravel-vite-plugin": "^1.2.0", + "lint-staged": "^16.2.7", "lodash": "^4.17.19", "postcss": "^8.4.19", "postcss-import": "^16.1.0", @@ -55,6 +67,7 @@ "openchemlib": "^7.4.3", "pluralize": "^8.0.0", "popper.js": "^1.16.1", + "qs": "^6.14.1", "vue-instantsearch": "^4.3.3", "vue-simple-context-menu": "^4.0.4", "vue3-clipboard": "^1.0.0", @@ -62,5 +75,9 @@ "vue3-slider": "^1.7.0", "vue3-tour": "^0.2.0", "vuedraggable": "^4.1.0" + }, + "overrides": { + "qs": "^6.14.1", + "preact": ">=10.26.10" } } diff --git a/phpunit.xml b/phpunit.xml index 5410ece8..0e487948 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,20 +7,43 @@ ./tests/Feature + + ./tests/API + - + + + + + + + - + + + + + + + + + + + ./app + + ./app/Console + ./app/Helper.php + diff --git a/public/build/manifest.json b/public/build/manifest.json index 96555c75..6d88997f 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -1,479 +1,520 @@ { - "_AccessDialogue-DaBf4ai0.js": { - "file": "assets/AccessDialogue-DaBf4ai0.js", + "_AccessDialogue-6epYdJnh.js": { + "file": "assets/AccessDialogue-6epYdJnh.js", "name": "AccessDialogue", "imports": [ "resources/js/app.js", - "_ActionMessage-uxM-o-da.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_ToolTip-B-BuMZla.js", - "_transition-K5DyIAHH.js" + "_ActionMessage-DFngKrgF.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_ToolTip-DFcOtIlt.js", + "_transition-4SmTCBb8.js" ] }, - "_ActionMessage-uxM-o-da.js": { - "file": "assets/ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js": { + "file": "assets/ActionMessage-DFngKrgF.js", "name": "ActionMessage", "imports": [ "resources/js/app.js" ] }, - "_ActionSection-DoJrofwP.js": { - "file": "assets/ActionSection-DoJrofwP.js", + "_ActionSection-CCyNy73c.js": { + "file": "assets/ActionSection-CCyNy73c.js", "name": "ActionSection", "imports": [ - "_SectionTitle-rK4YId1t.js", + "_SectionTitle-DF3zXPQO.js", "resources/js/app.js" ] }, - "_AnnouncementBanner-B_CIbGMm.js": { - "file": "assets/AnnouncementBanner-B_CIbGMm.js", + "_AnnouncementBanner-8dY3yqls.js": { + "file": "assets/AnnouncementBanner-8dY3yqls.js", "name": "AnnouncementBanner", "imports": [ "resources/js/app.js", - "_XMarkIcon-BrZ2BUzq.js" + "_XMarkIcon-D87qk0wX.js" ] }, - "_AppLayout-shqwEEI1.js": { - "file": "assets/AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js": { + "file": "assets/AppLayout-DQ-CT5DM.js", "name": "AppLayout", "imports": [ - "_ApplicationLogo-Byie6Ini.js", - "resources/js/app.js", - "_transition-K5DyIAHH.js", - "_ToolTip-B-BuMZla.js", - "_form-Cn9CuD1E.js", - "_use-outside-click-UcI2wRsE.js", - "_use-text-value-BpD-bIcx.js", - "_hidden-2_Kmyvd6.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_ApplicationLogo-BCqsC71e.js", + "resources/js/app.js", + "_ToolTip-DFcOtIlt.js", + "_transition-4SmTCBb8.js", + "_form-CCHxUUNA.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_hidden-DJBgiHDT.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", "resources/js/Pages/Study/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_vue-tags-input-BGwEuaGX.js", - "_InputError-EInikEKW.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_SelectRich-Bh8aQhO2.js" - ] - }, - "_ApplicationLogo-Byie6Ini.js": { - "file": "assets/ApplicationLogo-Byie6Ini.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_vue-tags-input-BIckaaPb.js", + "_InputError-6XL62mha.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_SelectRich-DrrR8e6B.js" + ] + }, + "_ApplicationLogo-BCqsC71e.js": { + "file": "assets/ApplicationLogo-BCqsC71e.js", "name": "ApplicationLogo", "imports": [ "resources/js/app.js" ] }, - "_AuthenticationCard-qrykQH--.js": { - "file": "assets/AuthenticationCard-qrykQH--.js", - "name": "AuthenticationCard", - "imports": [ - "resources/js/app.js" - ] + "_AuthenticationCardLogo-Ch3PkjAO.css": { + "file": "assets/AuthenticationCardLogo-Ch3PkjAO.css", + "src": "_AuthenticationCardLogo-Ch3PkjAO.css" }, - "_AuthenticationCardLogo-B3Uq11MZ.js": { - "file": "assets/AuthenticationCardLogo-B3Uq11MZ.js", + "_AuthenticationCardLogo-Dvnrf-ZC.js": { + "file": "assets/AuthenticationCardLogo-Dvnrf-ZC.js", "name": "AuthenticationCardLogo", "imports": [ "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js" + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/AuthenticationCardLogo-Ch3PkjAO.css" + ] + }, + "_AuthorCard-C3beQYsP.js": { + "file": "assets/AuthorCard-C3beQYsP.js", + "name": "AuthorCard", + "imports": [ + "_Tag-nzNsDjqQ.js", + "resources/js/app.js" ] }, - "_BreadCrumbs-Db7NAUm-.js": { - "file": "assets/BreadCrumbs-Db7NAUm-.js", + "_BreadCrumbs-CSDDwIdE.js": { + "file": "assets/BreadCrumbs-CSDDwIdE.js", "name": "BreadCrumbs", "imports": [ "resources/js/app.js" ] }, - "_Button-Do-BJmWY.js": { - "file": "assets/Button-Do-BJmWY.js", + "_Button-CYcxP55R.js": { + "file": "assets/Button-CYcxP55R.js", "name": "Button", "imports": [ "resources/js/app.js" ] }, - "_CalendarDaysIcon-bzmmtrVA.js": { - "file": "assets/CalendarDaysIcon-bzmmtrVA.js", + "_CalendarDaysIcon-BnoNmS-b.js": { + "file": "assets/CalendarDaysIcon-BnoNmS-b.js", "name": "CalendarDaysIcon", "imports": [ "resources/js/app.js" ] }, - "_Checkbox-CmyOWzuc.js": { - "file": "assets/Checkbox-CmyOWzuc.js", + "_Checkbox-M8tZcP39.js": { + "file": "assets/Checkbox-M8tZcP39.js", "name": "Checkbox", "imports": [ "resources/js/app.js" ] }, - "_CircleStackIcon-NWc2meE7.js": { - "file": "assets/CircleStackIcon-NWc2meE7.js", + "_CircleStackIcon-CS5bZzoF.js": { + "file": "assets/CircleStackIcon-CS5bZzoF.js", "name": "CircleStackIcon", "imports": [ "resources/js/app.js" ] }, - "_Citation-Dj91Ydnp.js": { - "file": "assets/Citation-Dj91Ydnp.js", + "_Citation-CwWfK607.js": { + "file": "assets/Citation-CwWfK607.js", "name": "Citation", "imports": [ "resources/js/app.js" ] }, - "_CitationCard-mr9Srs1M.js": { - "file": "assets/CitationCard-mr9Srs1M.js", + "_CitationCard-CuTkatpQ.js": { + "file": "assets/CitationCard-CuTkatpQ.js", "name": "CitationCard", "imports": [ - "_Tag-DRPRU5B7.js", "resources/js/app.js" ] }, - "_ClipboardDocumentIcon-DCnLMHiO.js": { - "file": "assets/ClipboardDocumentIcon-DCnLMHiO.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js": { + "file": "assets/ClipboardDocumentIcon-Ug5lWu_z.js", "name": "ClipboardDocumentIcon", "imports": [ "resources/js/app.js" ] }, - "_ConfirmationModal-BqBcQKy5.js": { - "file": "assets/ConfirmationModal-BqBcQKy5.js", + "_ConfirmationModal-Doiit_fw.js": { + "file": "assets/ConfirmationModal-Doiit_fw.js", "name": "ConfirmationModal", "imports": [ - "_Modal-D-_sV66W.js", + "_Modal-Bd_ss7mR.js", "resources/js/app.js" ] }, - "_DOIBadge-BBho5C2T.js": { - "file": "assets/DOIBadge-BBho5C2T.js", + "_DOIBadge-CejwrrtX.js": { + "file": "assets/DOIBadge-CejwrrtX.js", "name": "DOIBadge", "imports": [ "resources/js/app.js" ] }, - "_DangerButton-DhWljAF_.js": { - "file": "assets/DangerButton-DhWljAF_.js", + "_DangerButton-B282wGL0.js": { + "file": "assets/DangerButton-B282wGL0.js", "name": "DangerButton", "imports": [ "resources/js/app.js" ] }, - "_Depictor2D-BloOWuSj.js": { - "file": "assets/Depictor2D-BloOWuSj.js", + "_Depictor2D-BK1bz0fk.js": { + "file": "assets/Depictor2D-BK1bz0fk.js", "name": "Depictor2D", "imports": [ "resources/js/app.js" ] }, - "_Depictor3D-DpHRqJcL.js": { - "file": "assets/Depictor3D-DpHRqJcL.js", + "_Depictor3D-P55_wSnt.js": { + "file": "assets/Depictor3D-P55_wSnt.js", "name": "Depictor3D", "imports": [ "resources/js/app.js" ] }, - "_DialogModal-CmZ6e9E-.js": { - "file": "assets/DialogModal-CmZ6e9E-.js", + "_DialogModal-CjKGmYo8.js": { + "file": "assets/DialogModal-CjKGmYo8.js", "name": "DialogModal", "imports": [ - "_Modal-D-_sV66W.js", + "_Modal-Bd_ss7mR.js", "resources/js/app.js" ] }, - "_FlashMessages-H7Dz0t6O.js": { - "file": "assets/FlashMessages-H7Dz0t6O.js", + "_EmptySearchState-D8iMVcoP.js": { + "file": "assets/EmptySearchState-D8iMVcoP.js", + "name": "EmptySearchState", + "imports": [ + "resources/js/app.js" + ] + }, + "_FlashMessages-DIIt-rHI.js": { + "file": "assets/FlashMessages-DIIt-rHI.js", "name": "FlashMessages", "imports": [ "resources/js/app.js" ] }, - "_FormSection-D_YXuO3u.js": { - "file": "assets/FormSection-D_YXuO3u.js", + "_Footer-BKr9ElaS.js": { + "file": "assets/Footer-BKr9ElaS.js", + "name": "Footer", + "imports": [ + "resources/js/app.js", + "_ApplicationLogo-BCqsC71e.js" + ] + }, + "_FormSection-Dc9n8Inn.js": { + "file": "assets/FormSection-Dc9n8Inn.js", "name": "FormSection", "imports": [ - "_SectionTitle-rK4YId1t.js", + "_SectionTitle-DF3zXPQO.js", "resources/js/app.js" ] }, - "_HomeIcon-B3apTAEd.js": { - "file": "assets/HomeIcon-B3apTAEd.js", + "_HomeIcon-BDhDbrf1.js": { + "file": "assets/HomeIcon-BDhDbrf1.js", "name": "HomeIcon", "imports": [ "resources/js/app.js" ] }, - "_Icon-qHt8_t4q.js": { - "file": "assets/Icon-qHt8_t4q.js", + "_Icon-BGF2x0i_.js": { + "file": "assets/Icon-BGF2x0i_.js", "name": "Icon", "imports": [ "resources/js/app.js" ] }, - "_Input-B-ZTzbzW.js": { - "file": "assets/Input-B-ZTzbzW.js", + "_InboxIcon-YOZb0dE5.js": { + "file": "assets/InboxIcon-YOZb0dE5.js", + "name": "InboxIcon", + "imports": [ + "resources/js/app.js" + ] + }, + "_Input-AlXcTzUC.js": { + "file": "assets/Input-AlXcTzUC.js", "name": "Input", "imports": [ "resources/js/app.js" ] }, - "_InputError-EInikEKW.js": { - "file": "assets/InputError-EInikEKW.js", + "_InputError-6XL62mha.js": { + "file": "assets/InputError-6XL62mha.js", "name": "InputError", "imports": [ "resources/js/app.js" ] }, - "_Label-DGyfHV0z.js": { - "file": "assets/Label-DGyfHV0z.js", + "_Label-BLmemeCj.js": { + "file": "assets/Label-BLmemeCj.js", "name": "Label", "imports": [ "resources/js/app.js" ] }, - "_LoadingButton-DQ530rQU.js": { - "file": "assets/LoadingButton-DQ530rQU.js", + "_LoadingButton-BN1ujuvs.js": { + "file": "assets/LoadingButton-BN1ujuvs.js", "name": "LoadingButton", "imports": [ "resources/js/app.js" ] }, - "_LockClosedIcon-CK-FFoS7.js": { - "file": "assets/LockClosedIcon-CK-FFoS7.js", + "_LockClosedIcon-DEAA46dY.js": { + "file": "assets/LockClosedIcon-DEAA46dY.js", "name": "LockClosedIcon", "imports": [ "resources/js/app.js" ] }, - "_MagnifyingGlassIcon-Cq7Ou0Fo.js": { - "file": "assets/MagnifyingGlassIcon-Cq7Ou0Fo.js", + "_MagnifyingGlassIcon-CVqOcz6F.js": { + "file": "assets/MagnifyingGlassIcon-CVqOcz6F.js", "name": "MagnifyingGlassIcon", "imports": [ - "_portal-viea1unE.js", - "resources/js/app.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js" + "resources/js/app.js" ] }, - "_ManageCitation-A3-Z6aBb.js": { - "file": "assets/ManageCitation-A3-Z6aBb.js", + "_ManageCitation-CsZW89fU.js": { + "file": "assets/ManageCitation-CsZW89fU.js", "name": "ManageCitation", "imports": [ - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_InputError-EInikEKW.js", - "_LoadingButton-DQ530rQU.js", - "_DangerButton-DhWljAF_.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_InputError-6XL62mha.js", + "_LoadingButton-BN1ujuvs.js", + "_DangerButton-B282wGL0.js", "resources/js/app.js", - "_SelectRich-Bh8aQhO2.js", - "_CitationCard-mr9Srs1M.js", - "_AppLayout-shqwEEI1.js", - "_vue-tags-input-BGwEuaGX.js" + "_SelectRich-DrrR8e6B.js", + "_AuthorCard-C3beQYsP.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "_AppLayout-DQ-CT5DM.js", + "_vue-tags-input-BIckaaPb.js", + "_CitationCard-CuTkatpQ.js" ] }, - "_Modal-D-_sV66W.js": { - "file": "assets/Modal-D-_sV66W.js", + "_Modal-Bd_ss7mR.js": { + "file": "assets/Modal-Bd_ss7mR.js", "name": "Modal", "imports": [ "resources/js/app.js" ] }, - "_Pagination-DUPh-k9M.js": { - "file": "assets/Pagination-DUPh-k9M.js", + "_Pagination-BoSfljXU.js": { + "file": "assets/Pagination-BoSfljXU.js", "name": "Pagination", "imports": [ "resources/js/app.js" ] }, - "_ProjectCard-C7JLsgIL.js": { - "file": "assets/ProjectCard-C7JLsgIL.js", + "_ProjectCard-DxR9WRdn.js": { + "file": "assets/ProjectCard-DxR9WRdn.js", "name": "ProjectCard", "imports": [ "resources/js/app.js", - "_Tag-DRPRU5B7.js", - "_ScaleIcon-CYgZpCW4.js", - "_ToolTip-B-BuMZla.js" + "_Tag-nzNsDjqQ.js", + "_ScaleIcon-CoSPW33R.js", + "_ToolTip-DFcOtIlt.js" ] }, - "_ScaleIcon-CYgZpCW4.js": { - "file": "assets/ScaleIcon-CYgZpCW4.js", + "_RorAffiliationTypeahead-BJQxNUcB.js": { + "file": "assets/RorAffiliationTypeahead-BJQxNUcB.js", + "name": "RorAffiliationTypeahead", + "imports": [ + "_Input-AlXcTzUC.js", + "resources/js/app.js" + ] + }, + "_ScaleIcon-CoSPW33R.js": { + "file": "assets/ScaleIcon-CoSPW33R.js", "name": "ScaleIcon", "imports": [ "resources/js/app.js" ] }, - "_SecondaryButton-BucXplhe.js": { - "file": "assets/SecondaryButton-BucXplhe.js", + "_SecondaryButton-twf5Dkgt.js": { + "file": "assets/SecondaryButton-twf5Dkgt.js", "name": "SecondaryButton", "imports": [ "resources/js/app.js" ] }, - "_SectionBorder-Dz4Jb6r4.js": { - "file": "assets/SectionBorder-Dz4Jb6r4.js", + "_SectionBorder-tAgpamcM.js": { + "file": "assets/SectionBorder-tAgpamcM.js", "name": "SectionBorder", "imports": [ "resources/js/app.js" ] }, - "_SectionTitle-rK4YId1t.js": { - "file": "assets/SectionTitle-rK4YId1t.js", + "_SectionTitle-DF3zXPQO.js": { + "file": "assets/SectionTitle-DF3zXPQO.js", "name": "SectionTitle", "imports": [ "resources/js/app.js" ] }, - "_SelectOrcidId-D9IuGi3f.js": { - "file": "assets/SelectOrcidId-D9IuGi3f.js", + "_SelectOrcidId-BPrWbAjc.js": { + "file": "assets/SelectOrcidId-BPrWbAjc.js", "name": "SelectOrcidId", "imports": [ - "_SecondaryButton-BucXplhe.js", - "_DialogModal-CmZ6e9E-.js", - "_LoadingButton-DQ530rQU.js", + "_SecondaryButton-twf5Dkgt.js", + "_DialogModal-CjKGmYo8.js", + "_LoadingButton-BN1ujuvs.js", "resources/js/app.js" ] }, - "_SelectRich-Bh8aQhO2.js": { - "file": "assets/SelectRich-Bh8aQhO2.js", + "_SelectRich-DrrR8e6B.js": { + "file": "assets/SelectRich-DrrR8e6B.js", "name": "SelectRich", "imports": [ "resources/js/app.js", - "_hidden-2_Kmyvd6.js", - "_use-outside-click-UcI2wRsE.js", + "_hidden-DJBgiHDT.js", + "_use-outside-click-DJTVsRzK.js", "_micro-task-CxIZtCgj.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js" + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js" ] }, - "_ShareIcon-Bhy_NH0G.js": { - "file": "assets/ShareIcon-Bhy_NH0G.js", + "_ShareIcon-CmhN-V3N.js": { + "file": "assets/ShareIcon-CmhN-V3N.js", "name": "ShareIcon", "imports": [ "resources/js/app.js" ] }, - "_ShowProjectDates-DEyhF2bt.js": { - "file": "assets/ShowProjectDates-DEyhF2bt.js", + "_ShowProjectDates-CqZxw97W.js": { + "file": "assets/ShowProjectDates-CqZxw97W.js", "name": "ShowProjectDates", "imports": [ "resources/js/app.js", - "_CalendarDaysIcon-bzmmtrVA.js" + "_CalendarDaysIcon-BnoNmS-b.js" ] }, - "_SpectraViewer-DAiohBoS.js": { - "file": "assets/SpectraViewer-DAiohBoS.js", + "_SpectraViewer-CxgZXllf.js": { + "file": "assets/SpectraViewer-CxgZXllf.js", "name": "SpectraViewer", "imports": [ "resources/js/app.js" ] }, - "_StarIcon-BYJ8kyod.js": { - "file": "assets/StarIcon-BYJ8kyod.js", + "_StarIcon-BzBgLGpU.js": { + "file": "assets/StarIcon-BzBgLGpU.js", "name": "StarIcon", "imports": [ "resources/js/app.js" ] }, - "_StructureSearch-BAHFDTET.js": { - "file": "assets/StructureSearch-BAHFDTET.js", + "_StructureSearch-BfktCZ2H.js": { + "file": "assets/StructureSearch-BfktCZ2H.js", "name": "StructureSearch", "imports": [ "resources/js/app.js", - "_ToolTip-B-BuMZla.js", - "_transition-K5DyIAHH.js" + "_ToolTip-DFcOtIlt.js", + "_transition-4SmTCBb8.js" ] }, - "_StudyCard-CZirq0ik.js": { - "file": "assets/StudyCard-CZirq0ik.js", + "_StudyCard-y0ko3dT6.js": { + "file": "assets/StudyCard-y0ko3dT6.js", "name": "StudyCard", "imports": [ "resources/js/app.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" ] }, - "_StudyCardPublic-03l-9aGV.js": { - "file": "assets/StudyCardPublic-03l-9aGV.js", + "_StudyCardPublic-Ck9msVnU.js": { + "file": "assets/StudyCardPublic-Ck9msVnU.js", "name": "StudyCardPublic", "imports": [ "resources/js/app.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" ] }, - "_SuccessButton-CQHcc1CH.js": { - "file": "assets/SuccessButton-CQHcc1CH.js", + "_SuccessButton-DBYyga-I.js": { + "file": "assets/SuccessButton-DBYyga-I.js", "name": "SuccessButton", "imports": [ "resources/js/app.js" ] }, - "_Tag-DRPRU5B7.js": { - "file": "assets/Tag-DRPRU5B7.js", + "_Tag-nzNsDjqQ.js": { + "file": "assets/Tag-nzNsDjqQ.js", "name": "Tag", "imports": [ "resources/js/app.js" ] }, - "_ToggleButton-C0L2QH2i.js": { - "file": "assets/ToggleButton-C0L2QH2i.js", + "_ToggleButton-DmlFT2zl.js": { + "file": "assets/ToggleButton-DmlFT2zl.js", "name": "ToggleButton", "imports": [ "resources/js/app.js", - "_switch-DyCbjYu6.js" + "_switch-B_dbNcaR.js" ] }, - "_ToolTip-B-BuMZla.js": { - "file": "assets/ToolTip-B-BuMZla.js", + "_ToolTip-DFcOtIlt.js": { + "file": "assets/ToolTip-DFcOtIlt.js", "name": "ToolTip", "imports": [ "resources/js/app.js", - "_use-outside-click-UcI2wRsE.js", - "_use-text-value-BpD-bIcx.js" + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js" + ], + "dynamicImports": [ + "resources/js/app.js" ] }, - "_ValidationErrors-Cs3aqeYy.js": { - "file": "assets/ValidationErrors-Cs3aqeYy.js", + "_ValidationErrors-CAVvEVPH.js": { + "file": "assets/ValidationErrors-CAVvEVPH.js", "name": "ValidationErrors", "imports": [ "resources/js/app.js" ] }, - "_XMarkIcon-BrZ2BUzq.js": { - "file": "assets/XMarkIcon-BrZ2BUzq.js", + "_XMarkIcon-D87qk0wX.js": { + "file": "assets/XMarkIcon-D87qk0wX.js", "name": "XMarkIcon", "imports": [ "resources/js/app.js" ] }, - "_description-D4q-ya34.js": { - "file": "assets/description-D4q-ya34.js", + "_description-B6IAtsWS.js": { + "file": "assets/description-B6IAtsWS.js", "name": "description", "imports": [ "resources/js/app.js" ] }, - "_form-Cn9CuD1E.js": { - "file": "assets/form-Cn9CuD1E.js", + "_form-CCHxUUNA.js": { + "file": "assets/form-CCHxUUNA.js", "name": "form", "imports": [ "resources/js/app.js" ] }, - "_hidden-2_Kmyvd6.js": { - "file": "assets/hidden-2_Kmyvd6.js", + "_hidden-DJBgiHDT.js": { + "file": "assets/hidden-DJBgiHDT.js", "name": "hidden", "imports": [ "resources/js/app.js" ] }, - "_index-CwURvEqp.js": { - "file": "assets/index-CwURvEqp.js", + "_index-CrSDdz1v.js": { + "file": "assets/index-CrSDdz1v.js", "name": "index", "imports": [ "resources/js/app.js" @@ -483,8 +524,8 @@ "file": "assets/main-CITaNMI7.css", "src": "_main-CITaNMI7.css" }, - "_main-DPI-_6Az.js": { - "file": "assets/main-DPI-_6Az.js", + "_main-Ci1yhGiA.js": { + "file": "assets/main-Ci1yhGiA.js", "name": "main", "imports": [ "resources/js/app.js" @@ -497,24 +538,34 @@ "file": "assets/micro-task-CxIZtCgj.js", "name": "micro-task" }, - "_pickBy-lvn3s5H6.js": { - "file": "assets/pickBy-lvn3s5H6.js", + "_pickBy-i-_EkF5D.js": { + "file": "assets/pickBy-i-_EkF5D.js", "name": "pickBy", "imports": [ "resources/js/app.js" ] }, - "_portal-viea1unE.js": { - "file": "assets/portal-viea1unE.js", + "_popover-DYl4pGZ6.js": { + "file": "assets/popover-DYl4pGZ6.js", + "name": "popover", + "imports": [ + "_portal-CQaJgV45.js", + "resources/js/app.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js" + ] + }, + "_portal-CQaJgV45.js": { + "file": "assets/portal-CQaJgV45.js", "name": "portal", "imports": [ - "_use-outside-click-UcI2wRsE.js", + "_use-outside-click-DJTVsRzK.js", "resources/js/app.js", - "_hidden-2_Kmyvd6.js" + "_hidden-DJBgiHDT.js" ] }, - "_style-2QlRVEvL.js": { - "file": "assets/style-2QlRVEvL.js", + "_style-CYuQSOVW.js": { + "file": "assets/style-CYuQSOVW.js", "name": "style", "imports": [ "resources/js/app.js" @@ -527,2034 +578,2306 @@ "file": "assets/style-D0KUwiFO.css", "src": "_style-D0KUwiFO.css" }, - "_switch-DyCbjYu6.js": { - "file": "assets/switch-DyCbjYu6.js", + "_switch-B_dbNcaR.js": { + "file": "assets/switch-B_dbNcaR.js", "name": "switch", "imports": [ - "_form-Cn9CuD1E.js", + "_form-CCHxUUNA.js", "resources/js/app.js", - "_hidden-2_Kmyvd6.js", - "_description-D4q-ya34.js" + "_hidden-DJBgiHDT.js", + "_description-B6IAtsWS.js" ] }, - "_transition-K5DyIAHH.js": { - "file": "assets/transition-K5DyIAHH.js", + "_transition-4SmTCBb8.js": { + "file": "assets/transition-4SmTCBb8.js", "name": "transition", "imports": [ - "_portal-viea1unE.js", - "_hidden-2_Kmyvd6.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "resources/js/app.js", - "_use-outside-click-UcI2wRsE.js", + "_use-outside-click-DJTVsRzK.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js" + "_description-B6IAtsWS.js" ] }, - "_use-outside-click-UcI2wRsE.js": { - "file": "assets/use-outside-click-UcI2wRsE.js", + "_use-outside-click-DJTVsRzK.js": { + "file": "assets/use-outside-click-DJTVsRzK.js", "name": "use-outside-click", "imports": [ "resources/js/app.js" ] }, - "_use-text-value-BpD-bIcx.js": { - "file": "assets/use-text-value-BpD-bIcx.js", + "_use-text-value-XugEvGka.js": { + "file": "assets/use-text-value-XugEvGka.js", "name": "use-text-value", "imports": [ "resources/js/app.js" ] }, - "_vue-tags-input-BGwEuaGX.js": { - "file": "assets/vue-tags-input-BGwEuaGX.js", + "_vue-tags-input-BIckaaPb.js": { + "file": "assets/vue-tags-input-BIckaaPb.js", "name": "vue-tags-input", "imports": [ "resources/js/app.js" ] }, + "resources/css/app.css": { + "file": "assets/app-C9PyD9T5.css", + "src": "resources/css/app.css", + "isEntry": true + }, "resources/js/Pages/API/Index.vue": { - "file": "assets/Index-wDDwTVt7.js", + "file": "assets/Index-CZ7mVjDI.js", "name": "Index", "src": "resources/js/Pages/API/Index.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/API/Partials/ApiTokenManager.vue", - "_AppLayout-shqwEEI1.js", - "resources/js/app.js", - "_ActionMessage-uxM-o-da.js", - "_ActionSection-DoJrofwP.js", - "_SectionTitle-rK4YId1t.js", - "_Button-Do-BJmWY.js", - "_ConfirmationModal-BqBcQKy5.js", - "_Modal-D-_sV66W.js", - "_DangerButton-DhWljAF_.js", - "_DialogModal-CmZ6e9E-.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_Checkbox-CmyOWzuc.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_SecondaryButton-BucXplhe.js", - "_SectionBorder-Dz4Jb6r4.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "resources/js/app.js", + "_ActionMessage-DFngKrgF.js", + "_ActionSection-CCyNy73c.js", + "_SectionTitle-DF3zXPQO.js", + "_Button-CYcxP55R.js", + "_ConfirmationModal-Doiit_fw.js", + "_Modal-Bd_ss7mR.js", + "_DangerButton-B282wGL0.js", + "_DialogModal-CjKGmYo8.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_Checkbox-M8tZcP39.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_SecondaryButton-twf5Dkgt.js", + "_SectionBorder-tAgpamcM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js" + "_vue-tags-input-BIckaaPb.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/API/Partials/ApiTokenManager.vue": { - "file": "assets/ApiTokenManager-CrMpqUch.js", + "file": "assets/ApiTokenManager-DISCi7w8.js", "name": "ApiTokenManager", "src": "resources/js/Pages/API/Partials/ApiTokenManager.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_ActionSection-DoJrofwP.js", - "_Button-Do-BJmWY.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_DialogModal-CmZ6e9E-.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_Checkbox-CmyOWzuc.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_SecondaryButton-BucXplhe.js", - "_SectionBorder-Dz4Jb6r4.js", - "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_ActionMessage-DFngKrgF.js", + "_ActionSection-CCyNy73c.js", + "_Button-CYcxP55R.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_DialogModal-CjKGmYo8.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_Checkbox-M8tZcP39.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_SecondaryButton-twf5Dkgt.js", + "_SectionBorder-tAgpamcM.js", + "resources/js/app.js", + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/About.vue": { - "file": "assets/About-DsSjYaOL.js", + "file": "assets/About-QW-KHxgK.js", "name": "About", "src": "resources/js/Pages/About.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_FlashMessages-H7Dz0t6O.js", - "_MagnifyingGlassIcon-Cq7Ou0Fo.js", - "_XMarkIcon-BrZ2BUzq.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js" + "_ApplicationLogo-BCqsC71e.js", + "_FlashMessages-DIIt-rHI.js", + "_Footer-BKr9ElaS.js", + "_popover-DYl4pGZ6.js", + "_XMarkIcon-D87qk0wX.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_InboxIcon-YOZb0dE5.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js" + ], + "css": [ + "assets/About-B_ycmUNx.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Announcement/Index.vue": { - "file": "assets/Index-BZzqK1VE.js", + "file": "assets/Index-D3wAu81l.js", "name": "Index", "src": "resources/js/Pages/Announcement/Index.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_BreadCrumbs-Db7NAUm-.js", - "_Icon-qHt8_t4q.js", + "_AppLayout-DQ-CT5DM.js", + "_BreadCrumbs-CSDDwIdE.js", + "_Icon-BGF2x0i_.js", "resources/js/Pages/Announcement/Partials/Create.vue", "resources/js/Pages/Announcement/Partials/Edit.vue", - "_DangerButton-DhWljAF_.js", - "_SecondaryButton-BucXplhe.js", - "_DialogModal-CmZ6e9E-.js", - "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_DangerButton-B282wGL0.js", + "_SecondaryButton-twf5Dkgt.js", + "_DialogModal-CjKGmYo8.js", + "resources/js/app.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_Modal-D-_sV66W.js", - "_ToggleButton-C0L2QH2i.js", - "_main-DPI-_6Az.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_Modal-Bd_ss7mR.js", + "_ToggleButton-DmlFT2zl.js", + "_main-Ci1yhGiA.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Announcement/Partials/Create.vue": { - "file": "assets/Create-Dxec--Kk.js", + "file": "assets/Create-BHvWb62r.js", "name": "Create", "src": "resources/js/Pages/Announcement/Partials/Create.vue", "isDynamicEntry": true, "imports": [ - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_ToggleButton-C0L2QH2i.js", - "_main-DPI-_6Az.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_ToggleButton-DmlFT2zl.js", + "_main-Ci1yhGiA.js", "resources/js/app.js", - "_Modal-D-_sV66W.js", - "_switch-DyCbjYu6.js", - "_form-Cn9CuD1E.js", - "_hidden-2_Kmyvd6.js", - "_description-D4q-ya34.js" + "_Modal-Bd_ss7mR.js", + "_switch-B_dbNcaR.js", + "_form-CCHxUUNA.js", + "_hidden-DJBgiHDT.js", + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Announcement/Partials/Edit.vue": { - "file": "assets/Edit-Dr7xby21.js", + "file": "assets/Edit-B7CgPAj_.js", "name": "Edit", "src": "resources/js/Pages/Announcement/Partials/Edit.vue", "isDynamicEntry": true, "imports": [ - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_ToggleButton-C0L2QH2i.js", - "_main-DPI-_6Az.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_ToggleButton-DmlFT2zl.js", + "_main-Ci1yhGiA.js", "resources/js/app.js", - "_Modal-D-_sV66W.js", - "_switch-DyCbjYu6.js", - "_form-Cn9CuD1E.js", - "_hidden-2_Kmyvd6.js", - "_description-D4q-ya34.js" + "_Modal-Bd_ss7mR.js", + "_switch-B_dbNcaR.js", + "_form-CCHxUUNA.js", + "_hidden-DJBgiHDT.js", + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/ConfirmPassword.vue": { - "file": "assets/ConfirmPassword-C3_l58cZ.js", + "file": "assets/ConfirmPassword-1GcULvub.js", "name": "ConfirmPassword", "src": "resources/js/Pages/Auth/ConfirmPassword.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "_ApplicationLogo-Byie6Ini.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/ForgotPassword.vue": { - "file": "assets/ForgotPassword-BesprtPD.js", + "file": "assets/ForgotPassword-dXcUf-G8.js", "name": "ForgotPassword", "src": "resources/js/Pages/Auth/ForgotPassword.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "_ApplicationLogo-Byie6Ini.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/Login.vue": { - "file": "assets/Login-C3yEUbjr.js", + "file": "assets/Login-CAjtgfcb.js", "name": "Login", "src": "resources/js/Pages/Auth/Login.vue", "isDynamicEntry": true, "imports": [ - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Checkbox-CmyOWzuc.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Checkbox-M8tZcP39.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", "resources/js/app.js", - "_AnnouncementBanner-B_CIbGMm.js", - "_ApplicationLogo-Byie6Ini.js", - "_XMarkIcon-BrZ2BUzq.js" + "_AnnouncementBanner-8dY3yqls.js", + "_ApplicationLogo-BCqsC71e.js", + "_XMarkIcon-D87qk0wX.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/Register.vue": { - "file": "assets/Register-DtrO_O1N.js", + "file": "assets/Register-BVBF10_f.js", "name": "Register", "src": "resources/js/Pages/Auth/Register.vue", "isDynamicEntry": true, "imports": [ - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Checkbox-CmyOWzuc.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "resources/js/app.js", - "_AnnouncementBanner-B_CIbGMm.js", - "_InputError-EInikEKW.js", - "_SelectOrcidId-D9IuGi3f.js", - "_ApplicationLogo-Byie6Ini.js", - "_XMarkIcon-BrZ2BUzq.js", - "_SecondaryButton-BucXplhe.js", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_LoadingButton-DQ530rQU.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Checkbox-M8tZcP39.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "resources/js/app.js", + "_AnnouncementBanner-8dY3yqls.js", + "_InputError-6XL62mha.js", + "_SelectOrcidId-BPrWbAjc.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "_ApplicationLogo-BCqsC71e.js", + "_XMarkIcon-D87qk0wX.js", + "_SecondaryButton-twf5Dkgt.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_LoadingButton-BN1ujuvs.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/ResetPassword.vue": { - "file": "assets/ResetPassword--TjZNsBn.js", + "file": "assets/ResetPassword-K5NP_34R.js", "name": "ResetPassword", "src": "resources/js/Pages/Auth/ResetPassword.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "_ApplicationLogo-Byie6Ini.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/SetPassword.vue": { - "file": "assets/SetPassword-Dw4CoBJs.js", + "file": "assets/SetPassword-CjcVg9Xh.js", "name": "SetPassword", "src": "resources/js/Pages/Auth/SetPassword.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "_ApplicationLogo-Byie6Ini.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/TwoFactorChallenge.vue": { - "file": "assets/TwoFactorChallenge-ua3B007q.js", + "file": "assets/TwoFactorChallenge-BC8eoDOt.js", "name": "TwoFactorChallenge", "src": "resources/js/Pages/Auth/TwoFactorChallenge.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ValidationErrors-Cs3aqeYy.js", - "_ApplicationLogo-Byie6Ini.js" + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ValidationErrors-CAVvEVPH.js", + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Auth/VerifyEmail.vue": { - "file": "assets/VerifyEmail-Dy2xDSk0.js", + "file": "assets/VerifyEmail-8F9pqH7G.js", "name": "VerifyEmail", "src": "resources/js/Pages/Auth/VerifyEmail.vue", "isDynamicEntry": true, "imports": [ - "_AuthenticationCard-qrykQH--.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_Button-Do-BJmWY.js", + "_AuthenticationCardLogo-Dvnrf-ZC.js", + "_Button-CYcxP55R.js", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js" + "_ApplicationLogo-BCqsC71e.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Index.vue": { - "file": "assets/Index-DNqCPZuu.js", + "file": "assets/Index-fio8hjcs.js", "name": "Index", "src": "resources/js/Pages/Console/Index.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_Icon-qHt8_t4q.js", - "_ToolTip-B-BuMZla.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_Icon-BGF2x0i_.js", + "_ToolTip-DFcOtIlt.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Spectra/Index.vue": { - "file": "assets/Index-xVZ32gep.js", + "file": "assets/Index-iW8aMQua.js", "name": "Index", "src": "resources/js/Pages/Console/Spectra/Index.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_BreadCrumbs-Db7NAUm-.js", + "_AppLayout-DQ-CT5DM.js", + "_BreadCrumbs-CSDDwIdE.js", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Spectra/Snapshot.vue": { - "file": "assets/Snapshot-PNdJOEoO.js", + "file": "assets/Snapshot-Ci0rouKb.js", "name": "Snapshot", "src": "resources/js/Pages/Console/Spectra/Snapshot.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_BreadCrumbs-Db7NAUm-.js", + "_AppLayout-DQ-CT5DM.js", + "_BreadCrumbs-CSDDwIdE.js", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Users/Create.vue": { - "file": "assets/Create-BnHnkykD.js", + "file": "assets/Create-vBzxri65.js", "name": "Create", "src": "resources/js/Pages/Console/Users/Create.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_BreadCrumbs-Db7NAUm-.js", + "_AppLayout-DQ-CT5DM.js", + "_BreadCrumbs-CSDDwIdE.js", "resources/js/Pages/Console/Users/Partials/UserProfile.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_FormSection-D_YXuO3u.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ActionMessage-uxM-o-da.js", - "_SelectOrcidId-D9IuGi3f.js", - "_LoadingButton-DQ530rQU.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_FormSection-Dc9n8Inn.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ActionMessage-DFngKrgF.js", + "_SelectOrcidId-BPrWbAjc.js", + "_LoadingButton-BN1ujuvs.js", + "_RorAffiliationTypeahead-BJQxNUcB.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Users/Edit.vue": { - "file": "assets/Edit-C6WGund9.js", + "file": "assets/Edit-CGm1hyxT.js", "name": "Edit", "src": "resources/js/Pages/Console/Users/Edit.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_BreadCrumbs-Db7NAUm-.js", + "_AppLayout-DQ-CT5DM.js", + "_BreadCrumbs-CSDDwIdE.js", "resources/js/Pages/Console/Users/Partials/UserProfile.vue", "resources/js/Pages/Console/Users/Partials/UserPassword.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_FormSection-D_YXuO3u.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ActionMessage-uxM-o-da.js", - "_SelectOrcidId-D9IuGi3f.js", - "_LoadingButton-DQ530rQU.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_FormSection-Dc9n8Inn.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ActionMessage-DFngKrgF.js", + "_SelectOrcidId-BPrWbAjc.js", + "_LoadingButton-BN1ujuvs.js", + "_RorAffiliationTypeahead-BJQxNUcB.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Users/Index.vue": { - "file": "assets/Index-Chvea0vj.js", + "file": "assets/Index-CP8GKxoI.js", "name": "Index", "src": "resources/js/Pages/Console/Users/Index.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_pickBy-lvn3s5H6.js", - "resources/js/app.js", - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_BreadCrumbs-Db7NAUm-.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_pickBy-i-_EkF5D.js", + "resources/js/app.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_BreadCrumbs-CSDDwIdE.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_Modal-D-_sV66W.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_Modal-Bd_ss7mR.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Users/Partials/UserPassword.vue": { - "file": "assets/UserPassword-CGZAv6P5.js", + "file": "assets/UserPassword-D4r8qWlR.js", "name": "UserPassword", "src": "resources/js/Pages/Console/Users/Partials/UserPassword.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", + "_ActionMessage-DFngKrgF.js", + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js" + "_SectionTitle-DF3zXPQO.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Console/Users/Partials/UserProfile.vue": { - "file": "assets/UserProfile-3N4JikwH.js", + "file": "assets/UserProfile-BW72grxv.js", "name": "UserProfile", "src": "resources/js/Pages/Console/Users/Partials/UserProfile.vue", "isDynamicEntry": true, "imports": [ - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_ActionMessage-uxM-o-da.js", - "_SecondaryButton-BucXplhe.js", - "_SelectOrcidId-D9IuGi3f.js", - "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_LoadingButton-DQ530rQU.js" + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_ActionMessage-DFngKrgF.js", + "_SecondaryButton-twf5Dkgt.js", + "_SelectOrcidId-BPrWbAjc.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "resources/js/app.js", + "_SectionTitle-DF3zXPQO.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_LoadingButton-BN1ujuvs.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Dashboard.vue": { - "file": "assets/Dashboard-CLVWDe8B.js", + "file": "assets/Dashboard-CV-ukd2B.js", "name": "Dashboard", "src": "resources/js/Pages/Dashboard.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Project/Index.vue", - "_StudyCard-CZirq0ik.js", - "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_ToolTip-B-BuMZla.js", - "_use-outside-click-UcI2wRsE.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_hidden-2_Kmyvd6.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_StudyCard-y0ko3dT6.js", + "resources/js/app.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_EmptySearchState-D8iMVcoP.js", + "_ToolTip-DFcOtIlt.js", + "_form-CCHxUUNA.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_hidden-DJBgiHDT.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", "_micro-task-CxIZtCgj.js", - "_switch-DyCbjYu6.js", - "_description-D4q-ya34.js", + "_switch-B_dbNcaR.js", + "_description-B6IAtsWS.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_StarIcon-BYJ8kyod.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js", - "_portal-viea1unE.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_StarIcon-BzBgLGpU.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js", + "_portal-CQaJgV45.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" + ] + }, + "resources/js/Pages/Predict.vue": { + "file": "assets/Predict-B-DDTsF0.js", + "name": "Predict", + "src": "resources/js/Pages/Predict.vue", + "isDynamicEntry": true, + "imports": [ + "resources/js/app.js", + "_ApplicationLogo-BCqsC71e.js", + "_FlashMessages-DIIt-rHI.js", + "_Footer-BKr9ElaS.js", + "_XMarkIcon-D87qk0wX.js", + "_popover-DYl4pGZ6.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/PrivacyPolicy.vue": { - "file": "assets/PrivacyPolicy-CAMk5yhX.js", + "file": "assets/PrivacyPolicy-DNY0XyxI.js", "name": "PrivacyPolicy", "src": "resources/js/Pages/PrivacyPolicy.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_ApplicationLogo-Byie6Ini.js" + "_ApplicationLogo-BCqsC71e.js", + "_Footer-BKr9ElaS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Partials/DeleteUserForm.vue": { - "file": "assets/DeleteUserForm-c8u_GeKx.js", + "file": "assets/DeleteUserForm-C80Umngi.js", "name": "DeleteUserForm", "src": "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_DangerButton-DhWljAF_.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_DangerButton-B282wGL0.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue": { - "file": "assets/LogoutOtherBrowserSessionsForm-CgVEg4qL.js", + "file": "assets/LogoutOtherBrowserSessionsForm-DjscBov5.js", "name": "LogoutOtherBrowserSessionsForm", "src": "resources/js/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_ActionSection-DoJrofwP.js", - "_Button-Do-BJmWY.js", - "_DialogModal-CmZ6e9E-.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", + "_ActionMessage-DFngKrgF.js", + "_ActionSection-CCyNy73c.js", + "_Button-CYcxP55R.js", + "_DialogModal-CjKGmYo8.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue": { - "file": "assets/TwoFactorAuthenticationForm-Be_MxRp_.js", + "file": "assets/TwoFactorAuthenticationForm-DTLLl7WO.js", "name": "TwoFactorAuthenticationForm", "src": "resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_Button-Do-BJmWY.js", - "_DialogModal-CmZ6e9E-.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", + "_ActionSection-CCyNy73c.js", + "_Button-CYcxP55R.js", + "_DialogModal-CjKGmYo8.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", "resources/js/app.js", - "_DangerButton-DhWljAF_.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_DangerButton-B282wGL0.js", + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue": { - "file": "assets/UpdatePasswordForm-D8gDPMFW.js", + "file": "assets/UpdatePasswordForm-C-6BLWve.js", "name": "UpdatePasswordForm", "src": "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", + "_ActionMessage-DFngKrgF.js", + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js" + "_SectionTitle-DF3zXPQO.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue": { - "file": "assets/UpdateProfileInformationForm-BdjXeRia.js", + "file": "assets/UpdateProfileInformationForm-CXASg1C-.js", "name": "UpdateProfileInformationForm", "src": "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", "isDynamicEntry": true, "imports": [ - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_ActionMessage-uxM-o-da.js", - "_SecondaryButton-BucXplhe.js", - "_SelectOrcidId-D9IuGi3f.js", - "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_LoadingButton-DQ530rQU.js" + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_ActionMessage-DFngKrgF.js", + "_SecondaryButton-twf5Dkgt.js", + "_SelectOrcidId-BPrWbAjc.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "resources/js/app.js", + "_SectionTitle-DF3zXPQO.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_LoadingButton-BN1ujuvs.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Profile/Show.vue": { - "file": "assets/Show-CzQxFZ0r.js", + "file": "assets/Show-F-uEiIeK.js", "name": "Show", "src": "resources/js/Pages/Profile/Show.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", - "_SectionBorder-Dz4Jb6r4.js", + "_SectionBorder-tAgpamcM.js", "resources/js/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue", "resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.vue", "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_ActionSection-DoJrofwP.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_ActionMessage-uxM-o-da.js", - "_FormSection-D_YXuO3u.js", - "_Label-DGyfHV0z.js", - "_SelectOrcidId-D9IuGi3f.js", - "_LoadingButton-DQ530rQU.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_ActionSection-CCyNy73c.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_ActionMessage-DFngKrgF.js", + "_FormSection-Dc9n8Inn.js", + "_Label-BLmemeCj.js", + "_SelectOrcidId-BPrWbAjc.js", + "_LoadingButton-BN1ujuvs.js", + "_RorAffiliationTypeahead-BJQxNUcB.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Index.vue": { - "file": "assets/Index-BANH-5Nf.js", + "file": "assets/Index-DIjt0hpb.js", "name": "Index", "src": "resources/js/Pages/Project/Index.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_StarIcon-BYJ8kyod.js", - "_CalendarDaysIcon-bzmmtrVA.js" + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_StarIcon-BzBgLGpU.js", + "_CalendarDaysIcon-BnoNmS-b.js" + ], + "css": [ + "assets/Index-tn0RQdqM.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Activity.vue": { - "file": "assets/Activity-HsIibMtO.js", + "file": "assets/Activity-qAUYz67h.js", "name": "Activity", "src": "resources/js/Pages/Project/Partials/Activity.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js" + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Archive.vue": { - "file": "assets/Archive-2S7froJ9.js", + "file": "assets/Archive-Di-2Jkoe.js", "name": "Archive", "src": "resources/js/Pages/Project/Partials/Archive.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_DangerButton-DhWljAF_.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_LoadingButton-DQ530rQU.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_DangerButton-B282wGL0.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_LoadingButton-BN1ujuvs.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Create.vue": { - "file": "assets/Create-uBrHqsep.js", + "file": "assets/Create-CbWVYewi.js", "name": "Create", "src": "resources/js/Pages/Project/Partials/Create.vue", "isDynamicEntry": true, "imports": [ - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", "resources/js/app.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", - "_Modal-D-_sV66W.js", - "_hidden-2_Kmyvd6.js", - "_use-outside-click-UcI2wRsE.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", + "_Modal-Bd_ss7mR.js", + "_hidden-DJBgiHDT.js", + "_use-outside-click-DJTVsRzK.js", "_micro-task-CxIZtCgj.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_description-D4q-ya34.js" + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Delete.vue": { - "file": "assets/Delete-CBncv3vl.js", + "file": "assets/Delete-CfHFrMk2.js", "name": "Delete", "src": "resources/js/Pages/Project/Partials/Delete.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_DangerButton-DhWljAF_.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_LoadingButton-DQ530rQU.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_DangerButton-B282wGL0.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_LoadingButton-BN1ujuvs.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Details.vue": { - "file": "assets/Details-D72sP5nr.js", + "file": "assets/Details-tPngG5zR.js", "name": "Details", "src": "resources/js/Pages/Project/Partials/Details.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/app.js", - "_InputError-EInikEKW.js", + "_InputError-6XL62mha.js", "resources/js/Pages/Project/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_SecondaryButton-BucXplhe.js", - "_SelectRich-Bh8aQhO2.js", - "_Button-Do-BJmWY.js", - "_vue-tags-input-BGwEuaGX.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_style-CYuQSOVW.js", + "_SecondaryButton-twf5Dkgt.js", + "_SelectRich-DrrR8e6B.js", + "_Button-CYcxP55R.js", + "_vue-tags-input-BIckaaPb.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js" + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Restore.vue": { - "file": "assets/Restore-9yIb-R9t.js", + "file": "assets/Restore-Tika3iqF.js", "name": "Restore", "src": "resources/js/Pages/Project/Partials/Restore.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_SuccessButton-CQHcc1CH.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_LoadingButton-DQ530rQU.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_SuccessButton-DBYyga-I.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_LoadingButton-BN1ujuvs.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Partials/Unarchive.vue": { - "file": "assets/Unarchive-CGt_XB2P.js", + "file": "assets/Unarchive-DBLBmdyV.js", "name": "Unarchive", "src": "resources/js/Pages/Project/Partials/Unarchive.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_DangerButton-DhWljAF_.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_LoadingButton-DQ530rQU.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_DangerButton-B282wGL0.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_LoadingButton-BN1ujuvs.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Settings.vue": { - "file": "assets/Settings-PrYT47Rc.js", + "file": "assets/Settings-j1fDxNnY.js", "name": "Settings", "src": "resources/js/Pages/Project/Settings.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", "resources/js/Pages/Project/Partials/Delete.vue", "resources/js/Pages/Project/Partials/Restore.vue", "resources/js/Pages/Project/Partials/Archive.vue", "resources/js/Pages/Project/Partials/Unarchive.vue", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_ActionSection-DoJrofwP.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_LoadingButton-DQ530rQU.js", - "_SuccessButton-CQHcc1CH.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_ActionSection-CCyNy73c.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_LoadingButton-BN1ujuvs.js", + "_SuccessButton-DBYyga-I.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Show.vue": { - "file": "assets/Show-CpO7pd3h.js", + "file": "assets/Show-CXp6dza_.js", "name": "Show", "src": "resources/js/Pages/Project/Show.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_AccessDialogue-DaBf4ai0.js", + "_AppLayout-DQ-CT5DM.js", + "_AccessDialogue-6epYdJnh.js", "resources/js/app.js", "resources/js/Pages/Study/Index.vue", "resources/js/Pages/Project/Partials/Details.vue", - "_ManageCitation-A3-Z6aBb.js", - "_ToolTip-B-BuMZla.js", - "_Citation-Dj91Ydnp.js", - "_CitationCard-mr9Srs1M.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_main-DPI-_6Az.js", - "_ConfirmationModal-BqBcQKy5.js", - "_SecondaryButton-BucXplhe.js", - "_SuccessButton-CQHcc1CH.js", - "_ShowProjectDates-DEyhF2bt.js", - "_StarIcon-BYJ8kyod.js", - "_transition-K5DyIAHH.js", - "_ApplicationLogo-Byie6Ini.js", - "_form-Cn9CuD1E.js", - "_use-outside-click-UcI2wRsE.js", - "_use-text-value-BpD-bIcx.js", - "_hidden-2_Kmyvd6.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_ManageCitation-CsZW89fU.js", + "_ToolTip-DFcOtIlt.js", + "_Citation-CwWfK607.js", + "_AuthorCard-C3beQYsP.js", + "_CitationCard-CuTkatpQ.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_main-Ci1yhGiA.js", + "_ConfirmationModal-Doiit_fw.js", + "_SecondaryButton-twf5Dkgt.js", + "_SuccessButton-DBYyga-I.js", + "_ShowProjectDates-CqZxw97W.js", + "_StarIcon-BzBgLGpU.js", + "_transition-4SmTCBb8.js", + "_ApplicationLogo-BCqsC71e.js", + "_form-CCHxUUNA.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_hidden-DJBgiHDT.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", "_micro-task-CxIZtCgj.js", - "_switch-DyCbjYu6.js", - "_description-D4q-ya34.js", + "_switch-B_dbNcaR.js", + "_description-B6IAtsWS.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_DangerButton-DhWljAF_.js", - "_ActionMessage-uxM-o-da.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_StudyCard-CZirq0ik.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js", + "_vue-tags-input-BIckaaPb.js", + "_DangerButton-B282wGL0.js", + "_ActionMessage-DFngKrgF.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_StudyCard-y0ko3dT6.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js", "resources/js/Pages/Project/Partials/Activity.vue", - "_portal-viea1unE.js", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_LoadingButton-DQ530rQU.js", - "_CalendarDaysIcon-bzmmtrVA.js" + "_portal-CQaJgV45.js", + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_LoadingButton-BN1ujuvs.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_CalendarDaysIcon-BnoNmS-b.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Project/Validation.vue": { - "file": "assets/Validation-bZ-nNA82.js", + "file": "assets/Validation-DKGZD4Tr.js", "name": "Validation", "src": "resources/js/Pages/Project/Validation.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Compounds.vue": { - "file": "assets/Compounds-BMz_txSB.js", + "file": "assets/Compounds-C_MU_D3w.js", "name": "Compounds", "src": "resources/js/Pages/Public/Compounds.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "_Depictor2D-BloOWuSj.js", + "_AppLayout-DQ-CT5DM.js", + "_Depictor2D-BK1bz0fk.js", "resources/js/app.js", - "_StructureSearch-BAHFDTET.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_StructureSearch-BfktCZ2H.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/Compounds-Cyxz3eRr.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Datasets.vue": { - "file": "assets/Datasets-Bk2IgHUa.js", + "file": "assets/Datasets-CgPOoCA4.js", "name": "Datasets", "src": "resources/js/Pages/Public/Datasets.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_ScaleIcon-CYgZpCW4.js", - "_pickBy-lvn3s5H6.js", - "_Pagination-DUPh-k9M.js", - "_ToolTip-B-BuMZla.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ScaleIcon-CoSPW33R.js", + "_pickBy-i-_EkF5D.js", + "_Pagination-BoSfljXU.js", + "_ToolTip-DFcOtIlt.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/Datasets-CzTU3bJO.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Embed/Dataset.vue": { - "file": "assets/Dataset-C_iBn6GR.js", + "file": "assets/Dataset-DJJoLGiV.js", "name": "Dataset", "src": "resources/js/Pages/Public/Embed/Dataset.vue", "isDynamicEntry": true, "imports": [ - "_SpectraViewer-DAiohBoS.js", + "_SpectraViewer-CxgZXllf.js", "resources/js/app.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Embed/Sample.vue": { - "file": "assets/Sample-SpmQRIgb.js", + "file": "assets/Sample-CU4pW_Gk.js", "name": "Sample", "src": "resources/js/Pages/Public/Embed/Sample.vue", "isDynamicEntry": true, "imports": [ - "_SpectraViewer-DAiohBoS.js", + "_SpectraViewer-CxgZXllf.js", "resources/js/app.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Dataset.vue": { - "file": "assets/Dataset-CpEyPbx5.js", + "file": "assets/Dataset-_xRC_VNJ.js", "name": "Dataset", "src": "resources/js/Pages/Public/Project/Dataset.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", - "_SpectraViewer-DAiohBoS.js", - "_DOIBadge-BBho5C2T.js", - "_Depictor2D-BloOWuSj.js", - "resources/js/app.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_ShareIcon-Bhy_NH0G.js", - "_ToolTip-B-BuMZla.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_SpectraViewer-CxgZXllf.js", + "_DOIBadge-CejwrrtX.js", + "_Depictor2D-BK1bz0fk.js", + "resources/js/app.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_ShareIcon-CmhN-V3N.js", + "_ToolTip-DFcOtIlt.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Files.vue": { - "file": "assets/Files-CpkSF9_N.js", + "file": "assets/Files-B26NfCSy.js", "name": "Files", "src": "resources/js/Pages/Public/Project/Files.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", - "_AppLayout-shqwEEI1.js", - "resources/js/app.js", - "_HomeIcon-B3apTAEd.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "resources/js/app.js", + "_HomeIcon-BDhDbrf1.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Layout.vue": { - "file": "assets/Layout-DNjK53lj.js", + "file": "assets/Layout-C0Zqmzk3.js", "name": "Layout", "src": "resources/js/Pages/Public/Project/Layout.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "resources/js/app.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "resources/js/app.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/License.vue": { - "file": "assets/License-CIYsdsG1.js", + "file": "assets/License-CCPKWdVb.js", "name": "License", "src": "resources/js/Pages/Public/Project/License.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", "resources/js/app.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Samples.vue": { - "file": "assets/Samples-D9Ch_3Y7.js", + "file": "assets/Samples-D8Td5oF-.js", "name": "Samples", "src": "resources/js/Pages/Public/Project/Samples.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", - "_StudyCardPublic-03l-9aGV.js", - "resources/js/app.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_StudyCardPublic-Ck9msVnU.js", + "resources/js/app.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Show.vue": { - "file": "assets/Show-BAnUmhkW.js", + "file": "assets/Show-xWvUs0HZ.js", "name": "Show", "src": "resources/js/Pages/Public/Project/Show.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", - "_CitationCard-mr9Srs1M.js", - "_index-CwURvEqp.js", - "_Citation-Dj91Ydnp.js", - "resources/js/app.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AuthorCard-C3beQYsP.js", + "_CitationCard-CuTkatpQ.js", + "_index-CrSDdz1v.js", + "_Citation-CwWfK607.js", + "resources/js/app.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_DOIBadge-BBho5C2T.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_DOIBadge-CejwrrtX.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Project/Study.vue": { - "file": "assets/Study-Qa5hdwJy.js", + "file": "assets/Study-ByNcVHfy.js", "name": "Study", "src": "resources/js/Pages/Public/Project/Study.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Project/Layout.vue", - "_SpectraViewer-DAiohBoS.js", - "_Depictor2D-BloOWuSj.js", - "_Depictor3D-DpHRqJcL.js", - "_DOIBadge-BBho5C2T.js", - "resources/js/app.js", - "_Citation-Dj91Ydnp.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_ShareIcon-Bhy_NH0G.js", - "_ToolTip-B-BuMZla.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_SpectraViewer-CxgZXllf.js", + "_Depictor2D-BK1bz0fk.js", + "_Depictor3D-P55_wSnt.js", + "_DOIBadge-CejwrrtX.js", + "resources/js/app.js", + "_Citation-CwWfK607.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_ShareIcon-CmhN-V3N.js", + "_ToolTip-DFcOtIlt.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_ScaleIcon-CYgZpCW4.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_ScaleIcon-CoSPW33R.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Projects.vue": { - "file": "assets/Projects-C0nlvgGM.js", + "file": "assets/Projects-BcuGEDvj.js", "name": "Projects", "src": "resources/js/Pages/Public/Projects.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_pickBy-lvn3s5H6.js", - "_AppLayout-shqwEEI1.js", - "_ProjectCard-C7JLsgIL.js", - "_Pagination-DUPh-k9M.js", - "_ToolTip-B-BuMZla.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_pickBy-i-_EkF5D.js", + "_AppLayout-DQ-CT5DM.js", + "_ProjectCard-DxR9WRdn.js", + "_Pagination-BoSfljXU.js", + "_ToolTip-DFcOtIlt.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ScaleIcon-CYgZpCW4.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ScaleIcon-CoSPW33R.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/Projects-CyRAIvDh.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Sample/Layout.vue": { - "file": "assets/Layout-DWaWL8_r.js", + "file": "assets/Layout-DWLP_BNN.js", "name": "Layout", "src": "resources/js/Pages/Public/Sample/Layout.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Sample/Show.vue": { - "file": "assets/Show-CFKNZClV.js", + "file": "assets/Show-Ccob2xin.js", "name": "Show", "src": "resources/js/Pages/Public/Sample/Show.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Public/Sample/Layout.vue", - "_SpectraViewer-DAiohBoS.js", - "_Depictor2D-BloOWuSj.js", - "_DOIBadge-BBho5C2T.js", - "resources/js/app.js", - "_Citation-Dj91Ydnp.js", - "_ShowProjectDates-DEyhF2bt.js", - "_Depictor3D-DpHRqJcL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_ShareIcon-Bhy_NH0G.js", - "_ToolTip-B-BuMZla.js", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_SpectraViewer-CxgZXllf.js", + "_Depictor2D-BK1bz0fk.js", + "_DOIBadge-CejwrrtX.js", + "resources/js/app.js", + "_Citation-CwWfK607.js", + "_ShowProjectDates-CqZxw97W.js", + "_Depictor3D-P55_wSnt.js", + "_AuthorCard-C3beQYsP.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_ShareIcon-CmhN-V3N.js", + "_ToolTip-DFcOtIlt.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_CalendarDaysIcon-bzmmtrVA.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_Tag-nzNsDjqQ.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Public/Studies.vue": { - "file": "assets/Studies-DbbilOmo.js", + "file": "assets/Studies-BRqIVQ_c.js", "name": "Studies", "src": "resources/js/Pages/Public/Studies.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_StudyCardPublic-03l-9aGV.js", - "_pickBy-lvn3s5H6.js", - "_Pagination-DUPh-k9M.js", - "_ToolTip-B-BuMZla.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_StudyCardPublic-Ck9msVnU.js", + "_pickBy-i-_EkF5D.js", + "_Pagination-BoSfljXU.js", + "_ToolTip-DFcOtIlt.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js", + "_MagnifyingGlassIcon-CVqOcz6F.js" + ], + "css": [ + "assets/Studies-DZ0j2glf.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Publish.vue": { - "file": "assets/Publish-DVcN48gb.js", + "file": "assets/Publish-iiaOQq4A.js", "name": "Publish", "src": "resources/js/Pages/Publish.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "resources/js/app.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_vue-tags-input-BGwEuaGX.js", - "_main-DPI-_6Az.js", - "_ManageCitation-A3-Z6aBb.js", - "_CitationCard-mr9Srs1M.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js", - "_SelectRich-Bh8aQhO2.js", - "_ToggleButton-C0L2QH2i.js", - "_index-CwURvEqp.js", - "_ConfirmationModal-BqBcQKy5.js", - "_SuccessButton-CQHcc1CH.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "resources/js/app.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_vue-tags-input-BIckaaPb.js", + "_main-Ci1yhGiA.js", + "_ManageCitation-CsZW89fU.js", + "_AuthorCard-C3beQYsP.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js", + "_SelectRich-DrrR8e6B.js", + "_CitationCard-CuTkatpQ.js", + "_ToggleButton-DmlFT2zl.js", + "_index-CrSDdz1v.js", + "_ConfirmationModal-Doiit_fw.js", + "_SuccessButton-DBYyga-I.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_Button-Do-BJmWY.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_Button-CYcxP55R.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_DangerButton-DhWljAF_.js", - "_LoadingButton-DQ530rQU.js", - "_Tag-DRPRU5B7.js" + "_DangerButton-B282wGL0.js", + "_LoadingButton-BN1ujuvs.js", + "_RorAffiliationTypeahead-BJQxNUcB.js", + "_Input-AlXcTzUC.js", + "_Tag-nzNsDjqQ.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Recent.vue": { - "file": "assets/Recent-TJku6q1i.js", + "file": "assets/Recent-QUU8PDC5.js", "name": "Recent", "src": "resources/js/Pages/Recent.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Project/Index.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_StarIcon-BYJ8kyod.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_StarIcon-BzBgLGpU.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/SharedWithMe.vue": { - "file": "assets/SharedWithMe-fWP6Yees.js", + "file": "assets/SharedWithMe-DiRt67qb.js", "name": "SharedWithMe", "src": "resources/js/Pages/SharedWithMe.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Project/Index.vue", - "_StudyCard-CZirq0ik.js", - "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_StudyCard-y0ko3dT6.js", + "resources/js/app.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_StarIcon-BYJ8kyod.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_StarIcon-BzBgLGpU.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Starred.vue": { - "file": "assets/Starred-AO7GGajB.js", + "file": "assets/Starred-A2tUEo7o.js", "name": "Starred", "src": "resources/js/Pages/Starred.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Project/Index.vue", "resources/js/app.js", - "_StudyCard-CZirq0ik.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_StudyCard-y0ko3dT6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_StarIcon-BYJ8kyod.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_StarIcon-BzBgLGpU.js", + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/About.vue": { - "file": "assets/About-CSm3066P.js", + "file": "assets/About-BujIOiGu.js", "name": "About", "src": "resources/js/Pages/Study/About.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Study/Content.vue", - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_ToolTip-B-BuMZla.js", - "_InputError-EInikEKW.js", - "_Depictor2D-BloOWuSj.js", + "_ToolTip-DFcOtIlt.js", + "_InputError-6XL62mha.js", + "_Depictor2D-BK1bz0fk.js", + "_AuthorCard-C3beQYsP.js", "resources/js/Pages/Study/Layout.vue", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_style-2QlRVEvL.js", - "_Button-Do-BJmWY.js", - "_SelectRich-Bh8aQhO2.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_SecondaryButton-BucXplhe.js", - "_vue-tags-input-BGwEuaGX.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js", - "_ApplicationLogo-Byie6Ini.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_style-CYuQSOVW.js", + "_Button-CYcxP55R.js", + "_SelectRich-DrrR8e6B.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_SecondaryButton-twf5Dkgt.js", + "_vue-tags-input-BIckaaPb.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_CircleStackIcon-CS5bZzoF.js", + "_ApplicationLogo-BCqsC71e.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_Tag-nzNsDjqQ.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Content.vue": { - "file": "assets/Content-D9WfmHUt.js", + "file": "assets/Content-DHAhlg-E.js", "name": "Content", "src": "resources/js/Pages/Study/Content.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Study/Layout.vue", "resources/js/app.js", - "_CircleStackIcon-NWc2meE7.js", - "_AppLayout-shqwEEI1.js", + "_CircleStackIcon-CS5bZzoF.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", - "_InputError-EInikEKW.js", + "_ActionMessage-DFngKrgF.js", + "_InputError-6XL62mha.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_style-2QlRVEvL.js", - "_Button-Do-BJmWY.js", - "_SelectRich-Bh8aQhO2.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_SecondaryButton-BucXplhe.js", - "_vue-tags-input-BGwEuaGX.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_ToolTip-B-BuMZla.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_ApplicationLogo-Byie6Ini.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_style-CYuQSOVW.js", + "_Button-CYcxP55R.js", + "_SelectRich-DrrR8e6B.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_SecondaryButton-twf5Dkgt.js", + "_vue-tags-input-BIckaaPb.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_ToolTip-DFcOtIlt.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_ApplicationLogo-BCqsC71e.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Datasets.vue": { - "file": "assets/Datasets-DkPsuWgp.js", + "file": "assets/Datasets-DFu4v1G4.js", "name": "Datasets", "src": "resources/js/Pages/Study/Datasets.vue", "isDynamicEntry": true, "imports": [ "resources/js/Pages/Study/Content.vue", - "_LoadingButton-DQ530rQU.js", - "_SpectraViewer-DAiohBoS.js", - "_AppLayout-shqwEEI1.js", + "_LoadingButton-BN1ujuvs.js", + "_SpectraViewer-CxgZXllf.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_ShareIcon-Bhy_NH0G.js", - "_ToolTip-B-BuMZla.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_ShareIcon-CmhN-V3N.js", + "_ToolTip-DFcOtIlt.js", "resources/js/Pages/Study/Layout.vue", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", - "_InputError-EInikEKW.js", + "_ActionMessage-DFngKrgF.js", + "_InputError-6XL62mha.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_style-2QlRVEvL.js", - "_Button-Do-BJmWY.js", - "_SelectRich-Bh8aQhO2.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_SecondaryButton-BucXplhe.js", - "_vue-tags-input-BGwEuaGX.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_ApplicationLogo-Byie6Ini.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_style-CYuQSOVW.js", + "_Button-CYcxP55R.js", + "_SelectRich-DrrR8e6B.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_SecondaryButton-twf5Dkgt.js", + "_vue-tags-input-BIckaaPb.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_ApplicationLogo-BCqsC71e.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_CircleStackIcon-NWc2meE7.js" + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Files.vue": { - "file": "assets/Files-BM_oQcaU.js", + "file": "assets/Files-CFqmhWYh.js", "name": "Files", "src": "resources/js/Pages/Study/Files.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", "resources/js/Pages/Study/Content.vue", - "_ToolTip-B-BuMZla.js", - "_HomeIcon-B3apTAEd.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ToolTip-DFcOtIlt.js", + "_HomeIcon-BDhDbrf1.js", + "_ApplicationLogo-BCqsC71e.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", "resources/js/Pages/Study/Layout.vue", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Index.vue": { - "file": "assets/Index-C82ZXMLZ.js", + "file": "assets/Index-Dis0ZkgT.js", "name": "Index", "src": "resources/js/Pages/Study/Index.vue", "isDynamicEntry": true, "imports": [ - "_StudyCard-CZirq0ik.js", + "_StudyCard-y0ko3dT6.js", "resources/js/app.js", - "_Depictor2D-BloOWuSj.js", - "_LockClosedIcon-CK-FFoS7.js" + "_Depictor2D-BK1bz0fk.js", + "_LockClosedIcon-DEAA46dY.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Integrations.vue": { - "file": "assets/Integrations-CAsr5-W8.js", + "file": "assets/Integrations-BHqNw5QB.js", "name": "Integrations", "src": "resources/js/Pages/Study/Integrations.vue", "isDynamicEntry": true, @@ -2562,92 +2885,100 @@ "resources/js/Pages/Study/Content.vue", "resources/js/app.js", "resources/js/Pages/Study/Layout.vue", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Layout.vue": { - "file": "assets/Layout-DaLtTPjz.js", + "file": "assets/Layout-BAUv6V8u.js", "name": "Layout", "src": "resources/js/Pages/Study/Layout.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", "resources/js/Pages/Study/Partials/Details.vue", - "_AccessDialogue-DaBf4ai0.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AccessDialogue-6epYdJnh.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_ActionMessage-uxM-o-da.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/MolecularIdentifications.vue": { - "file": "assets/MolecularIdentifications-DeOIcevJ.js", + "file": "assets/MolecularIdentifications-BKFOJjhu.js", "name": "MolecularIdentifications", "src": "resources/js/Pages/Study/MolecularIdentifications.vue", "isDynamicEntry": true, @@ -2655,47 +2986,51 @@ "resources/js/Pages/Study/Content.vue", "resources/js/app.js", "resources/js/Pages/Study/Layout.vue", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Notifications.vue": { - "file": "assets/Notifications-Cyza3xAp.js", + "file": "assets/Notifications-DjtyglNW.js", "name": "Notifications", "src": "resources/js/Pages/Study/Notifications.vue", "isDynamicEntry": true, @@ -2703,129 +3038,145 @@ "resources/js/Pages/Study/Content.vue", "resources/js/app.js", "resources/js/Pages/Study/Layout.vue", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Partials/Activity.vue": { - "file": "assets/Activity-BHNnVIIB.js", + "file": "assets/Activity-C6IWQspj.js", "name": "Activity", "src": "resources/js/Pages/Study/Partials/Activity.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_use-outside-click-DJTVsRzK.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js" + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Partials/Create.vue": { - "file": "assets/Create-BVLvjQxD.js", + "file": "assets/Create-D3AaySjM.js", "name": "Create", "src": "resources/js/Pages/Study/Partials/Create.vue", "isDynamicEntry": true, "imports": [ - "_DialogModal-CmZ6e9E-.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", + "_DialogModal-CjKGmYo8.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", "resources/js/app.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", - "_Modal-D-_sV66W.js", - "_hidden-2_Kmyvd6.js", - "_use-outside-click-UcI2wRsE.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", + "_Modal-Bd_ss7mR.js", + "_hidden-DJBgiHDT.js", + "_use-outside-click-DJTVsRzK.js", "_micro-task-CxIZtCgj.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_description-D4q-ya34.js" + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Partials/Delete.vue": { - "file": "assets/Delete-uegnL0wb.js", + "file": "assets/Delete-D8-3ffZd.js", "name": "Delete", "src": "resources/js/Pages/Study/Partials/Delete.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_DialogModal-CmZ6e9E-.js", - "_DangerButton-DhWljAF_.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", + "_ActionSection-CCyNy73c.js", + "_DialogModal-CjKGmYo8.js", + "_DangerButton-B282wGL0.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Partials/Details.vue": { - "file": "assets/Details-BAAPFtXS.js", + "file": "assets/Details-uDpOLV3s.js", "name": "Details", "src": "resources/js/Pages/Study/Partials/Details.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_ActionMessage-uxM-o-da.js", - "_InputError-EInikEKW.js", + "_ActionMessage-DFngKrgF.js", + "_InputError-6XL62mha.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_Button-Do-BJmWY.js", - "_SelectRich-Bh8aQhO2.js", - "_SecondaryButton-BucXplhe.js", - "_vue-tags-input-BGwEuaGX.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_XMarkIcon-BrZ2BUzq.js", - "_transition-K5DyIAHH.js", - "_hidden-2_Kmyvd6.js", - "_use-outside-click-UcI2wRsE.js", + "_style-CYuQSOVW.js", + "_Button-CYcxP55R.js", + "_SelectRich-DrrR8e6B.js", + "_SecondaryButton-twf5Dkgt.js", + "_vue-tags-input-BIckaaPb.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_XMarkIcon-D87qk0wX.js", + "_transition-4SmTCBb8.js", + "_hidden-DJBgiHDT.js", + "_use-outside-click-DJTVsRzK.js", "_micro-task-CxIZtCgj.js", - "_form-Cn9CuD1E.js", - "_use-text-value-BpD-bIcx.js", - "_portal-viea1unE.js", - "_description-D4q-ya34.js" + "_form-CCHxUUNA.js", + "_use-text-value-XugEvGka.js", + "_portal-CQaJgV45.js", + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Protocols.vue": { - "file": "assets/Protocols-0MJ994Qs.js", + "file": "assets/Protocols-Cqcll44B.js", "name": "Protocols", "src": "resources/js/Pages/Study/Protocols.vue", "isDynamicEntry": true, @@ -2833,360 +3184,409 @@ "resources/js/Pages/Study/Content.vue", "resources/js/app.js", "resources/js/Pages/Study/Layout.vue", - "_AppLayout-shqwEEI1.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", "resources/js/Pages/Study/Partials/Details.vue", - "_ActionMessage-uxM-o-da.js", + "_ActionMessage-DFngKrgF.js", "resources/js/Pages/Study/Partials/Activity.vue", - "_style-2QlRVEvL.js", - "_ClipboardDocumentIcon-DCnLMHiO.js", - "_AccessDialogue-DaBf4ai0.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js", - "_Citation-Dj91Ydnp.js", - "_StarIcon-BYJ8kyod.js", - "_CircleStackIcon-NWc2meE7.js" + "_style-CYuQSOVW.js", + "_ClipboardDocumentIcon-Ug5lWu_z.js", + "_AccessDialogue-6epYdJnh.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js", + "_Citation-CwWfK607.js", + "_StarIcon-BzBgLGpU.js", + "_CircleStackIcon-CS5bZzoF.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Study/Settings.vue": { - "file": "assets/Settings-DJePdnOZ.js", + "file": "assets/Settings-Da8tbggI.js", "name": "Settings", "src": "resources/js/Pages/Study/Settings.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/app.js", "resources/js/Pages/Study/Partials/Delete.vue", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_ActionSection-DoJrofwP.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_ActionSection-CCyNy73c.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Create.vue": { - "file": "assets/Create-QXAAHtHo.js", + "file": "assets/Create-DLg26ZYN.js", "name": "Create", "src": "resources/js/Pages/Teams/Create.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Teams/Partials/CreateTeamForm.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_FormSection-D_YXuO3u.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_Label-DGyfHV0z.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_FormSection-Dc9n8Inn.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_Label-BLmemeCj.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Partials/CreateTeamForm.vue": { - "file": "assets/CreateTeamForm-DrMhQhpS.js", + "file": "assets/CreateTeamForm-BjuuwYM6.js", "name": "CreateTeamForm", "src": "resources/js/Pages/Teams/Partials/CreateTeamForm.vue", "isDynamicEntry": true, "imports": [ - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js" + "_SectionTitle-DF3zXPQO.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Partials/DeleteTeamForm.vue": { - "file": "assets/DeleteTeamForm-X8EJMnBB.js", + "file": "assets/DeleteTeamForm-CLYAhJkJ.js", "name": "DeleteTeamForm", "src": "resources/js/Pages/Teams/Partials/DeleteTeamForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionSection-DoJrofwP.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_SecondaryButton-BucXplhe.js", - "_InputError-EInikEKW.js", - "_Input-B-ZTzbzW.js", + "_ActionSection-CCyNy73c.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_SecondaryButton-twf5Dkgt.js", + "_InputError-6XL62mha.js", + "_Input-AlXcTzUC.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Partials/TeamMemberManager.vue": { - "file": "assets/TeamMemberManager-BJHjubQm.js", + "file": "assets/TeamMemberManager-HkGtp8uG.js", "name": "TeamMemberManager", "src": "resources/js/Pages/Teams/Partials/TeamMemberManager.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_ActionSection-DoJrofwP.js", - "_Button-Do-BJmWY.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_DialogModal-CmZ6e9E-.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", - "_SecondaryButton-BucXplhe.js", - "_SectionBorder-Dz4Jb6r4.js", - "resources/js/app.js", - "_SectionTitle-rK4YId1t.js", - "_Modal-D-_sV66W.js" + "_ActionMessage-DFngKrgF.js", + "_ActionSection-CCyNy73c.js", + "_Button-CYcxP55R.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_DialogModal-CjKGmYo8.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", + "_SecondaryButton-twf5Dkgt.js", + "_SectionBorder-tAgpamcM.js", + "resources/js/app.js", + "_SectionTitle-DF3zXPQO.js", + "_Modal-Bd_ss7mR.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Partials/UpdateTeamNameForm.vue": { - "file": "assets/UpdateTeamNameForm-Cx6F-aRP.js", + "file": "assets/UpdateTeamNameForm-BETnxISF.js", "name": "UpdateTeamNameForm", "src": "resources/js/Pages/Teams/Partials/UpdateTeamNameForm.vue", "isDynamicEntry": true, "imports": [ - "_ActionMessage-uxM-o-da.js", - "_Button-Do-BJmWY.js", - "_FormSection-D_YXuO3u.js", - "_Input-B-ZTzbzW.js", - "_InputError-EInikEKW.js", - "_Label-DGyfHV0z.js", + "_ActionMessage-DFngKrgF.js", + "_Button-CYcxP55R.js", + "_FormSection-Dc9n8Inn.js", + "_Input-AlXcTzUC.js", + "_InputError-6XL62mha.js", + "_Label-BLmemeCj.js", "resources/js/app.js", - "_SectionTitle-rK4YId1t.js" + "_SectionTitle-DF3zXPQO.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Teams/Show.vue": { - "file": "assets/Show-CcKcY4qN.js", + "file": "assets/Show-BMFvAcH3.js", "name": "Show", "src": "resources/js/Pages/Teams/Show.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Teams/Partials/DeleteTeamForm.vue", - "_SectionBorder-Dz4Jb6r4.js", + "_SectionBorder-tAgpamcM.js", "resources/js/Pages/Teams/Partials/TeamMemberManager.vue", "resources/js/Pages/Teams/Partials/UpdateTeamNameForm.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_ActionSection-DoJrofwP.js", - "_SectionTitle-rK4YId1t.js", - "_Input-B-ZTzbzW.js", - "_ActionMessage-uxM-o-da.js", - "_FormSection-D_YXuO3u.js", - "_Label-DGyfHV0z.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_ActionSection-CCyNy73c.js", + "_SectionTitle-DF3zXPQO.js", + "_Input-AlXcTzUC.js", + "_ActionMessage-DFngKrgF.js", + "_FormSection-Dc9n8Inn.js", + "_Label-BLmemeCj.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/TermsOfService.vue": { - "file": "assets/TermsOfService-DImG-bQc.js", + "file": "assets/TermsOfService-DVZNZoAw.js", "name": "TermsOfService", "src": "resources/js/Pages/TermsOfService.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_AuthenticationCardLogo-B3Uq11MZ.js", - "_ApplicationLogo-Byie6Ini.js" + "_ApplicationLogo-BCqsC71e.js", + "_Footer-BKr9ElaS.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Trashed.vue": { - "file": "assets/Trashed-WZAbWqOM.js", + "file": "assets/Trashed-AvlsQb3M.js", "name": "Trashed", "src": "resources/js/Pages/Trashed.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", + "_AppLayout-DQ-CT5DM.js", "resources/js/Pages/Project/Index.vue", "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_DialogModal-CmZ6e9E-.js", - "_Modal-D-_sV66W.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_InputError-EInikEKW.js", - "_SelectRich-Bh8aQhO2.js", - "_switch-DyCbjYu6.js", + "_DialogModal-CjKGmYo8.js", + "_Modal-Bd_ss7mR.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_InputError-6XL62mha.js", + "_SelectRich-DrrR8e6B.js", + "_switch-B_dbNcaR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_vue-tags-input-BGwEuaGX.js", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js", - "_Tag-DRPRU5B7.js", - "_ShowProjectDates-DEyhF2bt.js", - "_CalendarDaysIcon-bzmmtrVA.js", - "_StarIcon-BYJ8kyod.js" + "_vue-tags-input-BIckaaPb.js", + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js", + "_Tag-nzNsDjqQ.js", + "_ShowProjectDates-CqZxw97W.js", + "_CalendarDaysIcon-BnoNmS-b.js", + "_StarIcon-BzBgLGpU.js" + ], + "css": [ + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Upload.vue": { - "file": "assets/Upload-4gCobcYZ.js", + "file": "assets/Upload-CEJnHbi2.js", "name": "Upload", "src": "resources/js/Pages/Upload.vue", "isDynamicEntry": true, "imports": [ - "_AppLayout-shqwEEI1.js", - "resources/js/app.js", - "_InputError-EInikEKW.js", - "_SecondaryButton-BucXplhe.js", - "_Button-Do-BJmWY.js", - "_DialogModal-CmZ6e9E-.js", - "_Depictor2D-BloOWuSj.js", - "_Depictor3D-DpHRqJcL.js", - "_vue-tags-input-BGwEuaGX.js", - "_index-CwURvEqp.js", - "_SelectRich-Bh8aQhO2.js", - "_ApplicationLogo-Byie6Ini.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_AppLayout-DQ-CT5DM.js", + "resources/js/app.js", + "_InputError-6XL62mha.js", + "_SecondaryButton-twf5Dkgt.js", + "_Button-CYcxP55R.js", + "_DialogModal-CjKGmYo8.js", + "_EmptySearchState-D8iMVcoP.js", + "_Depictor2D-BK1bz0fk.js", + "_Depictor3D-P55_wSnt.js", + "_vue-tags-input-BIckaaPb.js", + "_index-CrSDdz1v.js", + "_SelectRich-DrrR8e6B.js", + "_ApplicationLogo-BCqsC71e.js", + "_ToolTip-DFcOtIlt.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_transition-4SmTCBb8.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_ToolTip-B-BuMZla.js", - "_use-text-value-BpD-bIcx.js", - "_form-Cn9CuD1E.js", - "_XMarkIcon-BrZ2BUzq.js", - "_FlashMessages-H7Dz0t6O.js", - "_AnnouncementBanner-B_CIbGMm.js", + "_description-B6IAtsWS.js", + "_form-CCHxUUNA.js", + "_XMarkIcon-D87qk0wX.js", + "_FlashMessages-DIIt-rHI.js", + "_AnnouncementBanner-8dY3yqls.js", "resources/js/Pages/Project/Partials/Create.vue", - "_switch-DyCbjYu6.js", - "_Modal-D-_sV66W.js", + "_switch-B_dbNcaR.js", + "_Modal-Bd_ss7mR.js", "resources/js/Pages/Study/Partials/Create.vue", - "_ConfirmationModal-BqBcQKy5.js", - "_DangerButton-DhWljAF_.js" + "_ConfirmationModal-Doiit_fw.js", + "_DangerButton-B282wGL0.js" + ], + "css": [ + "assets/Upload-DJ9akTOy.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/Pages/Welcome.vue": { - "file": "assets/Welcome-Dr7UzoV3.js", + "file": "assets/Welcome-Ak7M3ZoP.js", "name": "Welcome", "src": "resources/js/Pages/Welcome.vue", "isDynamicEntry": true, "imports": [ "resources/js/app.js", - "_ApplicationLogo-Byie6Ini.js", - "_ProjectCard-C7JLsgIL.js", - "_StructureSearch-BAHFDTET.js", - "_ToolTip-B-BuMZla.js", - "_FlashMessages-H7Dz0t6O.js", - "_CircleStackIcon-NWc2meE7.js", - "_XMarkIcon-BrZ2BUzq.js", - "_MagnifyingGlassIcon-Cq7Ou0Fo.js", - "_Tag-DRPRU5B7.js", - "_ScaleIcon-CYgZpCW4.js", - "_transition-K5DyIAHH.js", - "_portal-viea1unE.js", - "_use-outside-click-UcI2wRsE.js", - "_hidden-2_Kmyvd6.js", + "_ApplicationLogo-BCqsC71e.js", + "_ProjectCard-DxR9WRdn.js", + "_Footer-BKr9ElaS.js", + "_StructureSearch-BfktCZ2H.js", + "_ToolTip-DFcOtIlt.js", + "_FlashMessages-DIIt-rHI.js", + "_CircleStackIcon-CS5bZzoF.js", + "_XMarkIcon-D87qk0wX.js", + "_MagnifyingGlassIcon-CVqOcz6F.js", + "_InboxIcon-YOZb0dE5.js", + "_transition-4SmTCBb8.js", + "_popover-DYl4pGZ6.js", + "_Tag-nzNsDjqQ.js", + "_ScaleIcon-CoSPW33R.js", + "_use-outside-click-DJTVsRzK.js", + "_use-text-value-XugEvGka.js", + "_portal-CQaJgV45.js", + "_hidden-DJBgiHDT.js", "_micro-task-CxIZtCgj.js", - "_description-D4q-ya34.js", - "_use-text-value-BpD-bIcx.js" + "_description-B6IAtsWS.js" + ], + "css": [ + "assets/Welcome-Xmkb_-D4.css", + "assets/app-C9PyD9T5.css" ] }, "resources/js/app.js": { - "file": "assets/app-B0IVowKw.js", + "file": "assets/app-Wjah0i5w.js", "name": "app", "src": "resources/js/app.js", "isEntry": true, + "isDynamicEntry": true, "dynamicImports": [ "resources/js/Pages/API/Index.vue", "resources/js/Pages/API/Partials/ApiTokenManager.vue", @@ -3211,6 +3611,7 @@ "resources/js/Pages/Console/Users/Partials/UserPassword.vue", "resources/js/Pages/Console/Users/Partials/UserProfile.vue", "resources/js/Pages/Dashboard.vue", + "resources/js/Pages/Predict.vue", "resources/js/Pages/PrivacyPolicy.vue", "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", "resources/js/Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue", @@ -3275,7 +3676,7 @@ "resources/js/Pages/Welcome.vue" ], "css": [ - "assets/app-DisLJtzg.css" + "assets/app-C9PyD9T5.css" ] } } \ No newline at end of file diff --git a/public/img/FSU-Jena-logo.jpg b/public/img/FSU-Jena-logo.jpg new file mode 100644 index 00000000..7f825a57 Binary files /dev/null and b/public/img/FSU-Jena-logo.jpg differ diff --git a/public/img/UniversiteDeGeneve.png b/public/img/UniversiteDeGeneve.png new file mode 100644 index 00000000..0d428d80 Binary files /dev/null and b/public/img/UniversiteDeGeneve.png differ diff --git a/public/img/UniversiteParisSaclay.png b/public/img/UniversiteParisSaclay.png new file mode 100644 index 00000000..423854ab Binary files /dev/null and b/public/img/UniversiteParisSaclay.png differ diff --git a/public/img/eln/nobs.png b/public/img/eln/nobs.png new file mode 100644 index 00000000..f8ffa0f8 Binary files /dev/null and b/public/img/eln/nobs.png differ diff --git a/public/img/imbe-logo.png b/public/img/imbe-logo.png new file mode 100644 index 00000000..baff06f9 Binary files /dev/null and b/public/img/imbe-logo.png differ diff --git a/public/img/instrument-format.png b/public/img/instrument-format.png new file mode 100644 index 00000000..fa03a156 Binary files /dev/null and b/public/img/instrument-format.png differ diff --git a/public/img/metadata-format.png b/public/img/metadata-format.png new file mode 100644 index 00000000..014c90c4 Binary files /dev/null and b/public/img/metadata-format.png differ diff --git a/public/img/molecule-formats.png b/public/img/molecule-formats.png new file mode 100644 index 00000000..ad00d547 Binary files /dev/null and b/public/img/molecule-formats.png differ diff --git a/public/img/nmrxiv-logo.png b/public/img/nfdi4chem-logo.png similarity index 100% rename from public/img/nmrxiv-logo.png rename to public/img/nfdi4chem-logo.png diff --git a/public/img/old-logo.svg b/public/img/old-logo.svg deleted file mode 100644 index 8350812b..00000000 --- a/public/img/old-logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/public/img/ph.jpg b/public/img/ph.jpg deleted file mode 100644 index a987c261..00000000 Binary files a/public/img/ph.jpg and /dev/null differ diff --git a/public/img/spectra-format.png b/public/img/spectra-format.png new file mode 100644 index 00000000..85a7f6cf Binary files /dev/null and b/public/img/spectra-format.png differ diff --git a/public/img/spectra/1.png b/public/img/spectra/1.png new file mode 100644 index 00000000..222de088 Binary files /dev/null and b/public/img/spectra/1.png differ diff --git a/public/img/spectra/2.png b/public/img/spectra/2.png new file mode 100644 index 00000000..a9d452f0 Binary files /dev/null and b/public/img/spectra/2.png differ diff --git a/public/img/spectra/3.png b/public/img/spectra/3.png new file mode 100644 index 00000000..2bab4eb8 Binary files /dev/null and b/public/img/spectra/3.png differ diff --git a/public/img/spectra/4.png b/public/img/spectra/4.png new file mode 100644 index 00000000..188a43a5 Binary files /dev/null and b/public/img/spectra/4.png differ diff --git a/public/js/app.js b/public/js/app.js index 27433a8b..227dc30f 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -38472,14 +38472,14 @@ var footerNavigation = { href: "https://docs.nmrxiv.org" }, { name: "Guides", - href: "https://docs.nmrxiv.org/docs/submission-guides/overview" + href: "https://docs.nmrxiv.org/submission-guides/overview" }, { name: "API Status", - href: "https://docs.nmrxiv.org/docs/developer-guides/API" + href: "https://docs.nmrxiv.org/developer-guides/API" }], About: [{ name: "Adivsory Board", - href: "https://docs.nmrxiv.org/docs/contributing/contributors-and-steering-committee" + href: "https://docs.nmrxiv.org/contributing/contributors-and-steering-committee" } // { name: "Metrics", href: "#" }, // { name: "Blog", href: "#" }, // { name: "Press", href: "#" }, diff --git a/resources/js/App/FAQs.vue b/resources/js/App/FAQs.vue index 5b2a2ab6..72d54d44 100644 --- a/resources/js/App/FAQs.vue +++ b/resources/js/App/FAQs.vue @@ -1,49 +1,74 @@