Compare commits

..

22 Commits

Author SHA1 Message Date
Jason Rasmussen 6a4f48204d chore: pr feedback 2025-02-14 11:36:01 -05:00
Jason Rasmussen 9cd0871178 feat(web): rotate image 2025-02-14 11:17:51 -05:00
Zack Pollard dbbefde98d feat: native arm and amd64 server builds (#15408) 2025-02-14 15:55:18 +01:00
Jonathan Jogenfors 5407a28533 feat(server): Nullable asset dates (#15669)
* nullable dates

* wip

* don't search for null dates

* Add placeholder type

* cleanup
2025-02-13 15:30:12 -06:00
bo0tzz f5edc87e4d feat: comment URL on previewed PRs (#16085) 2025-02-13 21:10:00 +00:00
HelloMihai bf16b61d43 fix: broken html id (#16084)
ids cannot have spaces

relative should not be in the ID of the element
2025-02-13 14:46:12 -05:00
Joren Guillaume 8c882b54cd docs: put Windows restore command on one line (#16074)
Lots of 'unexpected newline' comments when restoring from other users, this should fix that.
2025-02-13 05:44:33 -05:00
Jason Rasmussen 2d7c333c8c refactor(server): narrow auth types (#16066) 2025-02-12 15:23:08 -05:00
Yaros 7c821dd205 feat(mobile): Made Map Bottom Sheet extendable higher (#16056)
Made Map Bottom Sheet extendable higher
2025-02-12 14:56:50 +00:00
renovate[bot] 703361da1a chore(deps): update dependency svelte to v5.19.9 (#16043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 17:24:39 -06:00
Jason Rasmussen fa5aeaf539 refactor: last repository (#16042) 2025-02-11 22:15:56 +00:00
Jason Rasmussen 5f3a42a132 refactor: repositories (#16038) 2025-02-11 15:12:31 -05:00
Jason Rasmussen 9d85272c2b refactor: repositories (#16036) 2025-02-11 14:08:13 -05:00
renovate[bot] d2575d8f00 fix(deps): update typescript-projects (#16023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-02-11 18:50:18 +00:00
renovate[bot] f0a4c945bd chore(deps): update github-actions (#16032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 17:24:47 +00:00
renovate[bot] a3766b879e fix(deps): update machine-learning (#16012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 11:23:54 -06:00
Alex 1a190c33a0 chore(mobile): post release task (#16004) 2025-02-11 11:23:02 -06:00
renovate[bot] 17a63e37b2 chore(deps): update base-image to v20250211 (major) (#16025)
chore(deps): update base-image to v20250211

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 11:21:25 -06:00
renovate[bot] bf1f8da884 chore(deps): update docker/build-push-action action to v6.13.0 (#16022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 14:16:10 +01:00
renovate[bot] 2271984dbd chore(deps): update dependency @types/node to ^22.13.1 (#16013)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 00:19:02 +00:00
Snowknight26 b40963ec52 fix(web): Update shared link Exif capitalization to match existing capitalization (#16010)
Update shared link Exif capitalization to match existing capitalization
2025-02-10 19:00:37 -05:00
Jason Rasmussen 735f8d661e refactor: test mocks (#16008) 2025-02-11 00:47:42 +01:00
251 changed files with 7393 additions and 7399 deletions
+3 -3
View File
@@ -56,10 +56,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0 uses: docker/setup-qemu-action@v3.4.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0 uses: docker/setup-buildx-action@v3.9.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@@ -88,7 +88,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@v6.12.0 uses: docker/build-push-action@v6.13.0
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+91 -44
View File
@@ -87,7 +87,6 @@ jobs:
TAG_NEW=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} TAG_NEW=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_NEW $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_NEW $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
build_and_push_ml: build_and_push_ml:
name: Build and Push ML name: Build and Push ML
needs: pre-job needs: pre-job
@@ -122,10 +121,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0 uses: docker/setup-qemu-action@v3.4.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0 uses: docker/setup-buildx-action@v3.9.0
- name: Login to Docker Hub - name: Login to Docker Hub
# Only push to Docker Hub when making a release # Only push to Docker Hub when making a release
@@ -174,7 +173,7 @@ jobs:
fi fi
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@v6.12.0 uses: docker/build-push-action@v6.13.0
with: with:
context: ${{ env.context }} context: ${{ env.context }}
file: ${{ env.file }} file: ${{ env.file }}
@@ -195,33 +194,38 @@ jobs:
build_and_push_server: build_and_push_server:
name: Build and Push Server name: Build and Push Server
runs-on: ubuntu-latest runs-on: ${{ matrix.runner }}
needs: pre-job needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_server == 'true' }} if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
env: env:
image: immich-server image: immich-server
context: . context: .
file: server/Dockerfile file: server/Dockerfile
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
DOCKER_REPO: altran1502/immich-server
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- platforms: linux/amd64,linux/arm64 - platform: linux/amd64
device: cpu runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps: steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
# Only push to Docker Hub when making a release
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@v3 uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -235,16 +239,81 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
id: build
uses: docker/build-push-action@v6.13.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
platforms: ${{ matrix.platform }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
labels: ${{ steps.metadata.outputs.labels }}
outputs: type=image,"name=${{ env.GHCR_REPO }},${{ env.DOCKER_REPO }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
DEVICE=cpu
BUILD_ID=${{ github.run_id }}
BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
BUILD_SOURCE_REF=${{ github.ref_name }}
BUILD_SOURCE_COMMIT=${{ github.sha }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge_server:
name: Merge & Push Server
runs-on: ubuntu-latest
if: ${{ needs.pre-job.outputs.should_run_server == 'true' && !github.event.pull_request.head.repo.fork }}
env:
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
DOCKER_REPO: altran1502/immich-server
needs:
- build_and_push_server
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Generate docker image tags - name: Generate docker image tags
id: metadata id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
flavor: | flavor: |
# Disable latest tag # Disable latest tag
latest=false latest=false
images: | images: |
name=ghcr.io/${{ github.repository_owner }}/${{env.image}} name=${{ env.GHCR_REPO }}
name=altran1502/${{env.image}},enable=${{ github.event_name == 'release' }} name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }}
tags: | tags: |
# Tag with branch name # Tag with branch name
type=ref,event=branch,suffix=${{ matrix.suffix }} type=ref,event=branch,suffix=${{ matrix.suffix }}
@@ -254,38 +323,16 @@ jobs:
type=ref,event=tag,suffix=${{ matrix.suffix }} type=ref,event=tag,suffix=${{ matrix.suffix }}
type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }} type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }}
- name: Determine build cache output - name: Create manifest list and push
id: cache-target working-directory: ${{ runner.temp }}/digests
run: | run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
# Essentially just ignore the cache output (PR can't write to registry cache) $(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) \
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *)
else
echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ env.image }}" >> $GITHUB_OUTPUT
fi
- name: Build and push image
uses: docker/build-push-action@v6.12.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{env.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
build-args: |
DEVICE=${{ matrix.device }}
BUILD_ID=${{ github.run_id }}
BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
BUILD_SOURCE_REF=${{ github.ref_name }}
BUILD_SOURCE_COMMIT=${{ github.sha }}
success-check-server: success-check-server:
name: Docker Build & Push Server Success name: Docker Build & Push Server Success
needs: [build_and_push_server, retag_server] needs: [merge_server, retag_server]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
+17
View File
@@ -0,0 +1,17 @@
name: Preview comment
on:
pull_request:
types: [labeled]
jobs:
comment-status:
runs-on: ubuntu-latest
if: ${{ github.event.label.name == 'preview' }}
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@v2
with:
message-id: "preview-status"
message: "Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/"
+133 -129
View File
@@ -24,7 +24,7 @@
"@types/cli-progress": "^3.11.0", "@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
@@ -59,7 +59,7 @@
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
}, },
@@ -881,9 +881,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.18.0", "version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1482,9 +1482,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.10.9", "version": "22.13.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
"integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1498,21 +1498,21 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz",
"integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/type-utils": "8.20.0", "@typescript-eslint/type-utils": "8.23.0",
"@typescript-eslint/utils": "8.20.0", "@typescript-eslint/utils": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1528,16 +1528,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz",
"integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1553,14 +1553,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz",
"integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0" "@typescript-eslint/visitor-keys": "8.23.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1571,16 +1571,16 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz",
"integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/utils": "8.20.0", "@typescript-eslint/utils": "8.23.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1595,9 +1595,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz",
"integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1609,20 +1609,20 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz",
"integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1636,16 +1636,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz",
"integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.20.0" "@typescript-eslint/typescript-estree": "8.23.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1660,13 +1660,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz",
"integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -1691,9 +1691,9 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz",
"integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1714,8 +1714,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "3.0.3", "@vitest/browser": "3.0.5",
"vitest": "3.0.3" "vitest": "3.0.5"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -1724,14 +1724,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
"integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"chai": "^5.1.2", "chai": "^5.1.2",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -1740,13 +1740,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
"integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.17" "magic-string": "^0.30.17"
}, },
@@ -1767,9 +1767,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
"integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1780,38 +1780,38 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
"integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"pathe": "^2.0.1" "pathe": "^2.0.2"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
"integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.0.3", "@vitest/pretty-format": "3.0.5",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.1" "pathe": "^2.0.2"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
"integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1822,13 +1822,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
"integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.0.3", "@vitest/pretty-format": "3.0.5",
"loupe": "^3.1.2", "loupe": "^3.1.2",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -2334,9 +2334,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.18.0", "version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2345,7 +2345,7 @@
"@eslint/config-array": "^0.19.0", "@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.10.0", "@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.18.0", "@eslint/js": "9.19.0",
"@eslint/plugin-kit": "^0.2.5", "@eslint/plugin-kit": "^0.2.5",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -2407,9 +2407,9 @@
} }
}, },
"node_modules/eslint-plugin-prettier": { "node_modules/eslint-plugin-prettier": {
"version": "5.2.2", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
"integrity": "sha512-1yI3/hf35wmlq66C8yOyrujQnel+v5l1Vop5Cl2I6ylyNTT1JbuUUnV3/41PzwTzcyDp/oF0jWE3HXvcH5AQOQ==", "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2685,9 +2685,9 @@
"dev": true "dev": true
}, },
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.18.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@@ -3180,9 +3180,9 @@
"dev": true "dev": true
}, },
"node_modules/loupe": { "node_modules/loupe": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -3285,9 +3285,9 @@
} }
}, },
"node_modules/mock-fs": { "node_modules/mock-fs": {
"version": "5.4.1", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz",
"integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", "integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4166,9 +4166,9 @@
} }
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
"integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4288,15 +4288,15 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.0.11", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz",
"integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.24.2", "esbuild": "^0.24.2",
"postcss": "^8.4.49", "postcss": "^8.5.1",
"rollup": "^4.23.0" "rollup": "^4.30.1"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@@ -4360,16 +4360,16 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
"integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cac": "^6.7.14", "cac": "^6.7.14",
"debug": "^4.4.0", "debug": "^4.4.0",
"es-module-lexer": "^1.6.0", "es-module-lexer": "^1.6.0",
"pathe": "^2.0.1", "pathe": "^2.0.2",
"vite": "^5.0.0 || ^6.0.0" "vite": "^5.0.0 || ^6.0.0"
}, },
"bin": { "bin": {
@@ -4403,31 +4403,31 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
"integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "3.0.3", "@vitest/expect": "3.0.5",
"@vitest/mocker": "3.0.3", "@vitest/mocker": "3.0.5",
"@vitest/pretty-format": "^3.0.3", "@vitest/pretty-format": "^3.0.5",
"@vitest/runner": "3.0.3", "@vitest/runner": "3.0.5",
"@vitest/snapshot": "3.0.3", "@vitest/snapshot": "3.0.5",
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"chai": "^5.1.2", "chai": "^5.1.2",
"debug": "^4.4.0", "debug": "^4.4.0",
"expect-type": "^1.1.0", "expect-type": "^1.1.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.1", "pathe": "^2.0.2",
"std-env": "^3.8.0", "std-env": "^3.8.0",
"tinybench": "^2.9.0", "tinybench": "^2.9.0",
"tinyexec": "^0.3.2", "tinyexec": "^0.3.2",
"tinypool": "^1.0.2", "tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0", "tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.0.3", "vite-node": "3.0.5",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -4441,9 +4441,10 @@
}, },
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.0.3", "@vitest/browser": "3.0.5",
"@vitest/ui": "3.0.3", "@vitest/ui": "3.0.5",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@@ -4451,6 +4452,9 @@
"@edge-runtime/vm": { "@edge-runtime/vm": {
"optional": true "optional": true
}, },
"@types/debug": {
"optional": true
},
"@types/node": { "@types/node": {
"optional": true "optional": true
}, },
+1 -1
View File
@@ -20,7 +20,7 @@
"@types/cli-progress": "^3.11.0", "@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
@@ -77,9 +77,7 @@ docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up sleep 10 # Wait for Postgres server to start up
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command 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` # Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip`
cat < "/dump.sql" \ 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 --dbname=postgres --username=<DB_USERNAME>
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
exit # Exit the Docker shell exit # Exit the Docker shell
docker compose up -d # Start remainder of Immich apps docker compose up -d # Start remainder of Immich apps
``` ```
+444 -358
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -25,7 +25,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"@types/oidc-provider": "^8.5.1", "@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0", "@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
+1
View File
@@ -298,6 +298,7 @@ describe('/libraries', () => {
expect(status).toBe(204); expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library'); await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets } = await utils.searchAssets(admin.accessToken, { const { assets } = await utils.searchAssets(admin.accessToken, {
originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`, originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`,
+3
View File
@@ -602,6 +602,8 @@
"enable": "Enable", "enable": "Enable",
"enabled": "Enabled", "enabled": "Enabled",
"end_date": "End date", "end_date": "End date",
"rotate_left": "Rotate left",
"rotate_right": "Rotate right",
"error": "Error", "error": "Error",
"error_loading_image": "Error loading image", "error_loading_image": "Error loading image",
"error_title": "Error - Something went wrong", "error_title": "Error - Something went wrong",
@@ -644,6 +646,7 @@
"quota_higher_than_disk_size": "You set a quota higher than the disk size", "quota_higher_than_disk_size": "You set a quota higher than the disk size",
"repair_unable_to_check_items": "Unable to check {count, select, one {item} other {items}}", "repair_unable_to_check_items": "Unable to check {count, select, one {item} other {items}}",
"unable_to_add_album_users": "Unable to add users to album", "unable_to_add_album_users": "Unable to add users to album",
"unable_to_rotate_image": "Unable to rotate image",
"unable_to_add_assets_to_shared_link": "Unable to add assets to shared link", "unable_to_add_assets_to_shared_link": "Unable to add assets to shared link",
"unable_to_add_comment": "Unable to add comment", "unable_to_add_comment": "Unable to add comment",
"unable_to_add_exclusion_pattern": "Unable to add exclusion pattern", "unable_to_add_exclusion_pattern": "Unable to add exclusion pattern",
+2 -2
View File
@@ -1,6 +1,6 @@
ARG DEVICE=cpu ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:adb581d8ed80edd03efd4dcad66db115b9ce8de8522b01720b9f3e6146f0884c AS builder-cpu FROM python:3.11-bookworm@sha256:14b4620f59a90f163dfa6bd252b68743f9a41d494a9fde935f9d7669d98094bb AS builder-cpu
FROM builder-cpu AS builder-openvino FROM builder-cpu AS builder-openvino
@@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv
COPY poetry.lock pyproject.toml ./ COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
FROM python:3.11-slim-bookworm@sha256:6ed5bff4d7d377e2a27d9285553b8c21cfccc4f00881de1b24c9bc8d90016e82 AS prod-cpu FROM python:3.11-slim-bookworm@sha256:42420f737ba91d509fc60d5ed65ed0492678a90c561e1fa08786ae8ba8b52eda AS prod-cpu
FROM prod-cpu AS prod-openvino FROM prod-cpu AS prod-openvino
+156 -158
View File
@@ -758,23 +758,23 @@ test = ["pytest (>=6)"]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.115.6" version = "0.115.8"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"},
{file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"},
] ]
[package.dependencies] [package.dependencies]
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.40.0,<0.42.0" starlette = ">=0.40.0,<0.46.0"
typing-extensions = ">=4.8.0" typing-extensions = ">=4.8.0"
[package.extras] [package.extras]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
@@ -1331,13 +1331,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "huggingface-hub" name = "huggingface-hub"
version = "0.27.1" version = "0.28.1"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
{file = "huggingface_hub-0.27.1-py3-none-any.whl", hash = "sha256:1c5155ca7d60b60c2e2fc38cbb3ffb7f7c3adf48f824015b219af9061771daec"}, {file = "huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7"},
{file = "huggingface_hub-0.27.1.tar.gz", hash = "sha256:c004463ca870283909d715d20f066ebd6968c2207dae9393fdffb3c1d4d8f98b"}, {file = "huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae"},
] ]
[package.dependencies] [package.dependencies]
@@ -1350,13 +1350,13 @@ tqdm = ">=4.42.1"
typing-extensions = ">=3.7.4.3" typing-extensions = ">=3.7.4.3"
[package.extras] [package.extras]
all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
cli = ["InquirerPy (==0.3.4)"] cli = ["InquirerPy (==0.3.4)"]
dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"]
hf-transfer = ["hf-transfer (>=0.1.4)"] hf-transfer = ["hf-transfer (>=0.1.4)"]
inference = ["aiohttp"] inference = ["aiohttp"]
quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.5.0)"] quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"]
tensorflow = ["graphviz", "pydot", "tensorflow"] tensorflow = ["graphviz", "pydot", "tensorflow"]
tensorflow-testing = ["keras (<3.0)", "tensorflow"] tensorflow-testing = ["keras (<3.0)", "tensorflow"]
testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"]
@@ -1625,13 +1625,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]] [[package]]
name = "locust" name = "locust"
version = "2.32.6" version = "2.32.9"
description = "Developer-friendly load testing framework" description = "Developer-friendly load testing framework"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "locust-2.32.6-py3-none-any.whl", hash = "sha256:d5c0e4f73134415d250087034431cf3ea42ca695d3dee7f10812287cacb6c4ef"}, {file = "locust-2.32.9-py3-none-any.whl", hash = "sha256:d9447c26d2bbaec5a0ace7cadefa1a31820ed392234257b309965a43d5e8d26f"},
{file = "locust-2.32.6.tar.gz", hash = "sha256:6600cc308398e724764aacc56ccddf6cfcd0127c4c92dedd5c4979dd37ef5b15"}, {file = "locust-2.32.9.tar.gz", hash = "sha256:4c297afa5cdc3de15dfa79279576e5f33c1d69dd70006b51d079dcbd212201cc"},
] ]
[package.dependencies] [package.dependencies]
@@ -1649,8 +1649,8 @@ psutil = ">=5.9.1"
pywin32 = {version = "*", markers = "sys_platform == \"win32\""} pywin32 = {version = "*", markers = "sys_platform == \"win32\""}
pyzmq = ">=25.0.0" pyzmq = ">=25.0.0"
requests = [ requests = [
{version = ">=2.32.2", markers = "python_full_version > \"3.11.0\""},
{version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""}, {version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""},
{version = ">=2.32.2", markers = "python_full_version > \"3.11.0\""},
] ]
setuptools = ">=70.0.0" setuptools = ">=70.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
@@ -1893,49 +1893,43 @@ files = [
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.14.1" version = "1.15.0"
description = "Optional static typing for Python" description = "Optional static typing for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
{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.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
{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.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
{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.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
{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.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
{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.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
{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] [package.dependencies]
@@ -2181,94 +2175,98 @@ files = [
[package.dependencies] [package.dependencies]
numpy = [ 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.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.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.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]] [[package]]
name = "orjson" name = "orjson"
version = "3.10.14" version = "3.10.15"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{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.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"},
{file = "orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"},
{file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d"}, {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"},
{file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca"}, {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"},
{file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5"}, {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"},
{file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc"}, {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"},
{file = "orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b"}, {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"},
{file = "orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28"}, {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"},
{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.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"},
{file = "orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5"}, {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"},
{file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e"}, {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"},
{file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905"}, {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"},
{file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436"}, {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"},
{file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e"}, {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"},
{file = "orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d"}, {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"},
{file = "orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb"}, {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"},
{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.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"},
{file = "orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b"}, {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"},
{file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe"}, {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"},
{file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953"}, {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"},
{file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3"}, {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"},
{file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780"}, {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"},
{file = "orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1"}, {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"},
{file = "orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406"}, {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"},
{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.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"},
{file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"},
{file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"},
{file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"},
{file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"},
{file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"},
{file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010"}, {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"},
{file = "orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d"}, {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"},
{file = "orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364"}, {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"},
{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.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175abf3d20e737fec47261d278f95031736a49d7832a09ab684026528c4d96db"}, {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29ca1a93e035d570e8b791b6c0feddd403c6a5388bfe870bf2aa6bba1b9d9b8e"}, {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f77202c80e8ab5a1d1e9faf642343bee5aaf332061e1ada4e9147dbd9eb00c46"}, {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2ec73b7099b6a29b40a62e08a23b936423bd35529f8f55c42e27acccde7954"}, {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d1679df9f9cd9504f8dff24555c1eaabba8aad7f5914f28dab99e3c2552c9d"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"},
{file = "orjson-3.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691ab9a13834310a263664313e4f747ceb93662d14a8bdf20eb97d27ed488f16"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"},
{file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b11ed82054fce82fb74cea33247d825d05ad6a4015ecfc02af5fbce442fbf361"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"},
{file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:e70a1d62b8288677d48f3bea66c21586a5f999c64ecd3878edb7393e8d1b548d"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"},
{file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:16642f10c1ca5611251bd835de9914a4b03095e28a34c8ba6a5500b5074338bd"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"},
{file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3871bad546aa66c155e3f36f99c459780c2a392d502a64e23fb96d9abf338511"}, {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"},
{file = "orjson-3.10.14-cp38-cp38-win32.whl", hash = "sha256:0293a88815e9bb5c90af4045f81ed364d982f955d12052d989d844d6c4e50945"}, {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"},
{file = "orjson-3.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:6169d3868b190d6b21adc8e61f64e3db30f50559dfbdef34a1cd6c738d409dfc"}, {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"},
{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.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962c2ec0dcaf22b76dee9831fdf0c4a33d4bf9a257a2bc5d4adc00d5c8ad9034"}, {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21d3be4132f71ef1360385770474f29ea1538a242eef72ac4934fe142800e37f"}, {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28ed60597c149a9e3f5ad6dd9cebaee6fb2f0e3f2d159a4a2b9b862d4748860"}, {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e947f70167fe18469f2023644e91ab3d24f9aed69a5e1c78e2c81b9cea553fb"}, {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64410696c97a35af2432dea7bdc4ce32416458159430ef1b4beb79fd30093ad6"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"},
{file = "orjson-3.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8050a5d81c022561ee29cd2739de5b4445f3c72f39423fde80a63299c1892c52"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"},
{file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b49a28e30d3eca86db3fe6f9b7f4152fcacbb4a467953cd1b42b94b479b77956"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"},
{file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ca041ad20291a65d853a9523744eebc3f5a4b2f7634e99f8fe88320695ddf766"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"},
{file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d313a2998b74bb26e9e371851a173a9b9474764916f1fc7971095699b3c6e964"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"},
{file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7796692136a67b3e301ef9052bde6fe8e7bd5200da766811a3a608ffa62aaff0"}, {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"},
{file = "orjson-3.10.14-cp39-cp39-win32.whl", hash = "sha256:eee4bc767f348fba485ed9dc576ca58b0a9eac237f0e160f7a59bce628ed06b3"}, {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"},
{file = "orjson-3.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:96a1c0ee30fb113b3ae3c748fd75ca74a157ff4c58476c47db4d61518962a011"}, {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"},
{file = "orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed"}, {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"},
{file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"},
{file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"},
{file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"},
{file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"},
] ]
[[package]] [[package]]
@@ -2498,13 +2496,13 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.10.5" version = "2.10.6"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
] ]
[package.dependencies] [package.dependencies]
@@ -2712,13 +2710,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
[[package]] [[package]]
name = "pytest-asyncio" name = "pytest-asyncio"
version = "0.25.2" version = "0.25.3"
description = "Pytest support for asyncio" description = "Pytest support for asyncio"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075"}, {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"},
{file = "pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f"}, {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"},
] ]
[package.dependencies] [package.dependencies]
@@ -3049,29 +3047,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.9.2" version = "0.9.6"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
{file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
{file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
{file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
{file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
{file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
{file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
] ]
[[package]] [[package]]
+6 -6
View File
@@ -541,7 +541,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -685,7 +685,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -715,7 +715,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -748,7 +748,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -791,7 +791,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -831,7 +831,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 194;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
+2 -2
View File
@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.126.0</string> <string>1.126.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>193</string> <string>194</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true/> <true/>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
+19 -13
View File
@@ -59,9 +59,10 @@ class MapBottomSheet extends HookConsumerWidget {
child: DraggableScrollableSheet( child: DraggableScrollableSheet(
controller: sheetController, controller: sheetController,
minChildSize: sheetMinExtent, minChildSize: sheetMinExtent,
maxChildSize: 0.5, maxChildSize: 0.8,
initialChildSize: sheetMinExtent, initialChildSize: sheetMinExtent,
snap: true, snap: true,
snapSizes: [sheetMinExtent, 0.5, 0.8],
shouldCloseOnMinExtent: false, shouldCloseOnMinExtent: false,
builder: (ctx, scrollController) => MapAssetGrid( builder: (ctx, scrollController) => MapAssetGrid(
controller: scrollController, controller: scrollController,
@@ -78,18 +79,23 @@ class MapBottomSheet extends HookConsumerWidget {
), ),
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: bottomSheetOffset, valueListenable: bottomSheetOffset,
builder: (ctx, value, child) => Positioned( builder: (context, value, child) {
right: 0, return Positioned(
bottom: context.height * (value + 0.02), right: 0,
child: child!, bottom: context.height * (value + 0.02),
), child: AnimatedOpacity(
child: ElevatedButton( opacity: value < 0.8 ? 1 : 0,
onPressed: onZoomToLocation, duration: const Duration(milliseconds: 150),
style: ElevatedButton.styleFrom( child: ElevatedButton(
shape: const CircleBorder(), onPressed: onZoomToLocation,
), style: ElevatedButton.styleFrom(
child: const Icon(Icons.my_location), shape: const CircleBorder(),
), ),
child: const Icon(Icons.my_location),
),
),
);
},
), ),
], ],
); );
+104 -1
View File
@@ -20,6 +20,7 @@ class AssetBulkUpdateDto {
this.isFavorite, this.isFavorite,
this.latitude, this.latitude,
this.longitude, this.longitude,
this.orientation,
this.rating, this.rating,
}); });
@@ -67,6 +68,8 @@ class AssetBulkUpdateDto {
/// ///
num? longitude; num? longitude;
AssetBulkUpdateDtoOrientationEnum? orientation;
/// Minimum value: -1 /// Minimum value: -1
/// Maximum value: 5 /// Maximum value: 5
/// ///
@@ -86,6 +89,7 @@ class AssetBulkUpdateDto {
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.latitude == latitude && other.latitude == latitude &&
other.longitude == longitude && other.longitude == longitude &&
other.orientation == orientation &&
other.rating == rating; other.rating == rating;
@override @override
@@ -98,10 +102,11 @@ class AssetBulkUpdateDto {
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(latitude == null ? 0 : latitude!.hashCode) + (latitude == null ? 0 : latitude!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) + (longitude == null ? 0 : longitude!.hashCode) +
(orientation == null ? 0 : orientation!.hashCode) +
(rating == null ? 0 : rating!.hashCode); (rating == null ? 0 : rating!.hashCode);
@override @override
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating]'; String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, duplicateId=$duplicateId, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, orientation=$orientation, rating=$rating]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -136,6 +141,11 @@ class AssetBulkUpdateDto {
} else { } else {
// json[r'longitude'] = null; // json[r'longitude'] = null;
} }
if (this.orientation != null) {
json[r'orientation'] = this.orientation;
} else {
// json[r'orientation'] = null;
}
if (this.rating != null) { if (this.rating != null) {
json[r'rating'] = this.rating; json[r'rating'] = this.rating;
} else { } else {
@@ -162,6 +172,7 @@ class AssetBulkUpdateDto {
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
latitude: num.parse('${json[r'latitude']}'), latitude: num.parse('${json[r'latitude']}'),
longitude: num.parse('${json[r'longitude']}'), longitude: num.parse('${json[r'longitude']}'),
orientation: AssetBulkUpdateDtoOrientationEnum.fromJson(json[r'orientation']),
rating: num.parse('${json[r'rating']}'), rating: num.parse('${json[r'rating']}'),
); );
} }
@@ -214,3 +225,95 @@ class AssetBulkUpdateDto {
}; };
} }
class AssetBulkUpdateDtoOrientationEnum {
/// Instantiate a new enum with the provided [value].
const AssetBulkUpdateDtoOrientationEnum._(this.value);
/// The underlying value of this enum member.
final int value;
@override
String toString() => value.toString();
int toJson() => value;
static const number1 = AssetBulkUpdateDtoOrientationEnum._(1);
static const number2 = AssetBulkUpdateDtoOrientationEnum._(2);
static const number3 = AssetBulkUpdateDtoOrientationEnum._(3);
static const number4 = AssetBulkUpdateDtoOrientationEnum._(4);
static const number5 = AssetBulkUpdateDtoOrientationEnum._(5);
static const number6 = AssetBulkUpdateDtoOrientationEnum._(6);
static const number7 = AssetBulkUpdateDtoOrientationEnum._(7);
static const number8 = AssetBulkUpdateDtoOrientationEnum._(8);
/// List of all possible values in this [enum][AssetBulkUpdateDtoOrientationEnum].
static const values = <AssetBulkUpdateDtoOrientationEnum>[
number1,
number2,
number3,
number4,
number5,
number6,
number7,
number8,
];
static AssetBulkUpdateDtoOrientationEnum? fromJson(dynamic value) => AssetBulkUpdateDtoOrientationEnumTypeTransformer().decode(value);
static List<AssetBulkUpdateDtoOrientationEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetBulkUpdateDtoOrientationEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetBulkUpdateDtoOrientationEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [AssetBulkUpdateDtoOrientationEnum] to int,
/// and [decode] dynamic data back to [AssetBulkUpdateDtoOrientationEnum].
class AssetBulkUpdateDtoOrientationEnumTypeTransformer {
factory AssetBulkUpdateDtoOrientationEnumTypeTransformer() => _instance ??= const AssetBulkUpdateDtoOrientationEnumTypeTransformer._();
const AssetBulkUpdateDtoOrientationEnumTypeTransformer._();
int encode(AssetBulkUpdateDtoOrientationEnum data) => data.value;
/// Decodes a [dynamic value][data] to a AssetBulkUpdateDtoOrientationEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetBulkUpdateDtoOrientationEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case 1: return AssetBulkUpdateDtoOrientationEnum.number1;
case 2: return AssetBulkUpdateDtoOrientationEnum.number2;
case 3: return AssetBulkUpdateDtoOrientationEnum.number3;
case 4: return AssetBulkUpdateDtoOrientationEnum.number4;
case 5: return AssetBulkUpdateDtoOrientationEnum.number5;
case 6: return AssetBulkUpdateDtoOrientationEnum.number6;
case 7: return AssetBulkUpdateDtoOrientationEnum.number7;
case 8: return AssetBulkUpdateDtoOrientationEnum.number8;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [AssetBulkUpdateDtoOrientationEnumTypeTransformer] instance.
static AssetBulkUpdateDtoOrientationEnumTypeTransformer? _instance;
}
+104 -1
View File
@@ -20,6 +20,7 @@ class UpdateAssetDto {
this.latitude, this.latitude,
this.livePhotoVideoId, this.livePhotoVideoId,
this.longitude, this.longitude,
this.orientation,
this.rating, this.rating,
}); });
@@ -73,6 +74,8 @@ class UpdateAssetDto {
/// ///
num? longitude; num? longitude;
UpdateAssetDtoOrientationEnum? orientation;
/// Minimum value: -1 /// Minimum value: -1
/// Maximum value: 5 /// Maximum value: 5
/// ///
@@ -92,6 +95,7 @@ class UpdateAssetDto {
other.latitude == latitude && other.latitude == latitude &&
other.livePhotoVideoId == livePhotoVideoId && other.livePhotoVideoId == livePhotoVideoId &&
other.longitude == longitude && other.longitude == longitude &&
other.orientation == orientation &&
other.rating == rating; other.rating == rating;
@override @override
@@ -104,10 +108,11 @@ class UpdateAssetDto {
(latitude == null ? 0 : latitude!.hashCode) + (latitude == null ? 0 : latitude!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
(longitude == null ? 0 : longitude!.hashCode) + (longitude == null ? 0 : longitude!.hashCode) +
(orientation == null ? 0 : orientation!.hashCode) +
(rating == null ? 0 : rating!.hashCode); (rating == null ? 0 : rating!.hashCode);
@override @override
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating]'; String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, orientation=$orientation, rating=$rating]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -146,6 +151,11 @@ class UpdateAssetDto {
} else { } else {
// json[r'longitude'] = null; // json[r'longitude'] = null;
} }
if (this.orientation != null) {
json[r'orientation'] = this.orientation;
} else {
// json[r'orientation'] = null;
}
if (this.rating != null) { if (this.rating != null) {
json[r'rating'] = this.rating; json[r'rating'] = this.rating;
} else { } else {
@@ -170,6 +180,7 @@ class UpdateAssetDto {
latitude: num.parse('${json[r'latitude']}'), latitude: num.parse('${json[r'latitude']}'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'), livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
longitude: num.parse('${json[r'longitude']}'), longitude: num.parse('${json[r'longitude']}'),
orientation: UpdateAssetDtoOrientationEnum.fromJson(json[r'orientation']),
rating: num.parse('${json[r'rating']}'), rating: num.parse('${json[r'rating']}'),
); );
} }
@@ -221,3 +232,95 @@ class UpdateAssetDto {
}; };
} }
class UpdateAssetDtoOrientationEnum {
/// Instantiate a new enum with the provided [value].
const UpdateAssetDtoOrientationEnum._(this.value);
/// The underlying value of this enum member.
final int value;
@override
String toString() => value.toString();
int toJson() => value;
static const number1 = UpdateAssetDtoOrientationEnum._(1);
static const number2 = UpdateAssetDtoOrientationEnum._(2);
static const number3 = UpdateAssetDtoOrientationEnum._(3);
static const number4 = UpdateAssetDtoOrientationEnum._(4);
static const number5 = UpdateAssetDtoOrientationEnum._(5);
static const number6 = UpdateAssetDtoOrientationEnum._(6);
static const number7 = UpdateAssetDtoOrientationEnum._(7);
static const number8 = UpdateAssetDtoOrientationEnum._(8);
/// List of all possible values in this [enum][UpdateAssetDtoOrientationEnum].
static const values = <UpdateAssetDtoOrientationEnum>[
number1,
number2,
number3,
number4,
number5,
number6,
number7,
number8,
];
static UpdateAssetDtoOrientationEnum? fromJson(dynamic value) => UpdateAssetDtoOrientationEnumTypeTransformer().decode(value);
static List<UpdateAssetDtoOrientationEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <UpdateAssetDtoOrientationEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = UpdateAssetDtoOrientationEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [UpdateAssetDtoOrientationEnum] to int,
/// and [decode] dynamic data back to [UpdateAssetDtoOrientationEnum].
class UpdateAssetDtoOrientationEnumTypeTransformer {
factory UpdateAssetDtoOrientationEnumTypeTransformer() => _instance ??= const UpdateAssetDtoOrientationEnumTypeTransformer._();
const UpdateAssetDtoOrientationEnumTypeTransformer._();
int encode(UpdateAssetDtoOrientationEnum data) => data.value;
/// Decodes a [dynamic value][data] to a UpdateAssetDtoOrientationEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
UpdateAssetDtoOrientationEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case 1: return UpdateAssetDtoOrientationEnum.number1;
case 2: return UpdateAssetDtoOrientationEnum.number2;
case 3: return UpdateAssetDtoOrientationEnum.number3;
case 4: return UpdateAssetDtoOrientationEnum.number4;
case 5: return UpdateAssetDtoOrientationEnum.number5;
case 6: return UpdateAssetDtoOrientationEnum.number6;
case 7: return UpdateAssetDtoOrientationEnum.number7;
case 8: return UpdateAssetDtoOrientationEnum.number8;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [UpdateAssetDtoOrientationEnumTypeTransformer] instance.
static UpdateAssetDtoOrientationEnumTypeTransformer? _instance;
}
+30
View File
@@ -7963,6 +7963,21 @@
"longitude": { "longitude": {
"type": "number" "type": "number"
}, },
"orientation": {
"enum": [
1,
2,
3,
4,
5,
6,
7,
8
],
"maximum": 8,
"minimum": 1,
"type": "integer"
},
"rating": { "rating": {
"maximum": 5, "maximum": 5,
"minimum": -1, "minimum": -1,
@@ -12880,6 +12895,21 @@
"longitude": { "longitude": {
"type": "number" "type": "number"
}, },
"orientation": {
"enum": [
1,
2,
3,
4,
5,
6,
7,
8
],
"maximum": 8,
"minimum": 1,
"type": "integer"
},
"rating": { "rating": {
"maximum": 5, "maximum": 5,
"minimum": -1, "minimum": -1,
+4 -4
View File
@@ -12,7 +12,7 @@
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
}, },
@@ -22,9 +22,9 @@
"integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g==" "integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.10.9", "version": "22.13.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
"integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -19,7 +19,7 @@
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"repository": { "repository": {
@@ -391,6 +391,7 @@ export type AssetBulkUpdateDto = {
isFavorite?: boolean; isFavorite?: boolean;
latitude?: number; latitude?: number;
longitude?: number; longitude?: number;
orientation?: Orientation;
rating?: number; rating?: number;
}; };
export type AssetBulkUploadCheckItem = { export type AssetBulkUploadCheckItem = {
@@ -439,6 +440,7 @@ export type UpdateAssetDto = {
latitude?: number; latitude?: number;
livePhotoVideoId?: string | null; livePhotoVideoId?: string | null;
longitude?: number; longitude?: number;
orientation?: Orientation;
rating?: number; rating?: number;
}; };
export type AssetMediaReplaceDto = { export type AssetMediaReplaceDto = {
@@ -3481,6 +3483,16 @@ export enum AssetMediaStatus {
Replaced = "replaced", Replaced = "replaced",
Duplicate = "duplicate" Duplicate = "duplicate"
} }
export enum Orientation {
$1 = 1,
$2 = 2,
$3 = 3,
$4 = 4,
$5 = 5,
$6 = 6,
$7 = 7,
$8 = 8
}
export enum Action { export enum Action {
Accept = "accept", Accept = "accept",
Reject = "reject" Reject = "reject"
+2 -2
View File
@@ -1,5 +1,5 @@
# dev build # dev build
FROM ghcr.io/immich-app/base-server-dev:20250204@sha256:8b203f19f4d5cf4619b60ee5f50d6d4b5ea3745747f5e5170d1b7404ebeb0792 AS dev FROM ghcr.io/immich-app/base-server-dev:20250211@sha256:6ae577a6518e1ccca973db16955f4d79b01cac3ae122759ccb1c17bf6c330ba9 AS dev
RUN apt-get install --no-install-recommends -yqq tini RUN apt-get install --no-install-recommends -yqq tini
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -42,7 +42,7 @@ RUN npm run build
# prod build # prod build
FROM ghcr.io/immich-app/base-server-prod:20250204@sha256:2af3da713d5ab3ccca23b216b747557ea6016117e72deac101e35069ccaf9b5e FROM ghcr.io/immich-app/base-server-prod:20250211@sha256:879cfe3d2afd4b7bdb211f694f99b6c0679a8bbd3e96964ad6c878f8471d63ea
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production \ ENV NODE_ENV=production \
+244 -249
View File
@@ -19,7 +19,7 @@
"@nestjs/swagger": "^11.0.2", "@nestjs/swagger": "^11.0.2",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.0.4", "@nestjs/websockets": "^11.0.4",
"@opentelemetry/auto-instrumentations-node": "^0.55.0", "@opentelemetry/auto-instrumentations-node": "^0.56.0",
"@opentelemetry/context-async-hooks": "^1.24.0", "@opentelemetry/context-async-hooks": "^1.24.0",
"@opentelemetry/exporter-prometheus": "^0.57.0", "@opentelemetry/exporter-prometheus": "^0.57.0",
"@opentelemetry/sdk-node": "^0.57.0", "@opentelemetry/sdk-node": "^0.57.0",
@@ -86,7 +86,7 @@
"@types/lodash": "^4.14.197", "@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"@types/nodemailer": "^6.4.14", "@types/nodemailer": "^6.4.14",
"@types/picomatch": "^3.0.0", "@types/picomatch": "^3.0.0",
"@types/pngjs": "^6.0.5", "@types/pngjs": "^6.0.5",
@@ -1178,9 +1178,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.18.0", "version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2310,9 +2310,9 @@
] ]
}, },
"node_modules/@nestjs/bull-shared": { "node_modules/@nestjs/bull-shared": {
"version": "11.0.1", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.1.tgz", "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.2.tgz",
"integrity": "sha512-FqpmIjhCONaYo+5AjtggPdo2lRIM/fv1VHiEq7YwFZBTNSPW0eOvcT96JDb5q4OuvLvADxgpnwP7rmzZywMMiw==", "integrity": "sha512-dFlttJvBqIFD6M8JVFbkrR4Feb39OTAJPJpFVILU50NOJCM4qziRw3dSNG84Q3v+7/M6xUGMFdZRRGvBBKxoSA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
@@ -2323,12 +2323,12 @@
} }
}, },
"node_modules/@nestjs/bullmq": { "node_modules/@nestjs/bullmq": {
"version": "11.0.1", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-11.0.1.tgz", "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-11.0.2.tgz",
"integrity": "sha512-BntU0Zfiyk4R5hlasUV22n1HuqmWWKvsx3knSR5A9/5vce808pmHOmHrtm4GZDs/8Pw9X8UGY8zdLe4a36S6KQ==", "integrity": "sha512-Lq6lGpKkETsm0RDcUktlzsthFoE3A5QTMp2FwPi1eztKqKD6/90KS1TcnC9CJFzjpUaYnQzIMrlNs55e+/wsHA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nestjs/bull-shared": "^11.0.1", "@nestjs/bull-shared": "^11.0.2",
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"peerDependencies": { "peerDependencies": {
@@ -2577,9 +2577,9 @@
} }
}, },
"node_modules/@nestjs/common": { "node_modules/@nestjs/common": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.8.tgz",
"integrity": "sha512-j+M3WOU6loZPNirIHDiZ1LxXRXVNb62XicgLBqdgyrDBFCJrAZaq0lfERUEPlN0/j4GBFnTSPg+CNsoGTBW1zQ==", "integrity": "sha512-IB6wEl8RgT/vWzb6p3cmBwTY3R0qfQWvO8lW0PfIv4DTJfUiVqNNEikonGuH/6TX8KvRXNhXHCaQZrUN00Xe6g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"iterare": "1.2.1", "iterare": "1.2.1",
@@ -2606,9 +2606,9 @@
} }
}, },
"node_modules/@nestjs/core": { "node_modules/@nestjs/core": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.8.tgz",
"integrity": "sha512-Xf33bwc3waAJ/faJBW06+Dwq3m15p3wbFOc/CcK8ua5EZna4sMjIjXXAb6bQmEjR1KfTXV5z595UD2vwp6cyHg==", "integrity": "sha512-GQLLdZnjZOmV4Q+TzQ8YuHvEYOneRhzsDbSJRkKdFFAVuoVh+q1nWZy+bZNeTxdWZFGL2Rve70X5jc4MoSXJqQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2680,9 +2680,9 @@
} }
}, },
"node_modules/@nestjs/platform-express": { "node_modules/@nestjs/platform-express": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.8.tgz",
"integrity": "sha512-fP6vrpqDIBaf1FNfFtBeJm/BwtGtueatI4FHxaBgw93XxKmIOV4G4ZO7ouQKqfgyIxV2mkYr/Fhg7hwRmizIjw==", "integrity": "sha512-Ru7seOYYglKNGQFzNALE5ilLqkdtX/ge6AJDKLMt+WI7iElZ7lXjT40fE3+HVUiZODunmeKQ7jVxcQyZwLafVA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cors": "2.8.5", "cors": "2.8.5",
@@ -2701,9 +2701,9 @@
} }
}, },
"node_modules/@nestjs/platform-socket.io": { "node_modules/@nestjs/platform-socket.io": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.0.8.tgz",
"integrity": "sha512-80r6hp0p+YK/+D5srjPTE/fc1w0aTM6ecqprJr8bFlzTPJbtYtpfHqMbEg0UfRKTsZ0krEgpv8fb0K4dSYzW0w==", "integrity": "sha512-DUpfRSDgxu+z9czB6ddFdQFawSAIr7jEbNOvpjpjYErvDitUdos57FhTw9IJxIm2EAOHoiCk4g3tN59GfjdwfQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"socket.io": "4.8.1", "socket.io": "4.8.1",
@@ -2720,9 +2720,9 @@
} }
}, },
"node_modules/@nestjs/schedule": { "node_modules/@nestjs/schedule": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-5.0.1.tgz",
"integrity": "sha512-RHqJIOo3AQvdeq0WuIFDqa5N0CkgxgqwmWRla96S6GmFV6qkQD1//EeH4k19MeCu4Ac9PzZ2y/Hu0zK9f//BQg==", "integrity": "sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cron": "3.5.0" "cron": "3.5.0"
@@ -2916,9 +2916,9 @@
} }
}, },
"node_modules/@nestjs/testing": { "node_modules/@nestjs/testing": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.8.tgz",
"integrity": "sha512-RZDWdnOncOQ1vT3630VlRzKee2P21ZJoF1+NAY+nzYUuYuYAaBdjrTZQGwymmiZQcrM+TQaViSjSPUmcJXdKyA==", "integrity": "sha512-5Reqec4MQSm4nFasKE5Pd799cAx3MmjkweF17Wgj/EJNWhFgVdv6N9OUIWXbU8nc8Pjso1fJmv0KJyN6h51qOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2957,9 +2957,9 @@
} }
}, },
"node_modules/@nestjs/websockets": { "node_modules/@nestjs/websockets": {
"version": "11.0.6", "version": "11.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.6.tgz", "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.0.8.tgz",
"integrity": "sha512-bsYZnmIXmZTYWzLJ3LbusH72kdppKSQLKUe9cfuUPXuTF+FaAX6TvErP11dJJ0W0S1Iwufr+LERTK211Txs8Eg==", "integrity": "sha512-wyS512+QWhWhE8NU1DgbAPkCaaOSNK1xBIgRlgpYg5/tKuhu4lc5r8iMdZAQn6xay++ELlOqsSlyuj0J2BixOA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"iterare": "1.2.1", "iterare": "1.2.1",
@@ -3186,9 +3186,9 @@
} }
}, },
"node_modules/@opentelemetry/auto-instrumentations-node": { "node_modules/@opentelemetry/auto-instrumentations-node": {
"version": "0.55.3", "version": "0.56.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.55.3.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.56.0.tgz",
"integrity": "sha512-tX5k3ZG8Nk6f1DHAF0K1ClP/OiW2hNuSeCVqDHNMcJ58dZSiad0XO2mwrvSipo77/DPXXUl0j9MxqmUVITdujQ==", "integrity": "sha512-d1X3DQY0+VmhNUir/3U3JO6Uh0FOSm8G91zsPzVVKc6NGDwmHP6Dn7PMVH70O6FZ0yErzlHqRx8vkNiAsTWt5A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/instrumentation": "^0.57.0",
@@ -3198,7 +3198,7 @@
"@opentelemetry/instrumentation-bunyan": "^0.45.0", "@opentelemetry/instrumentation-bunyan": "^0.45.0",
"@opentelemetry/instrumentation-cassandra-driver": "^0.45.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.45.0",
"@opentelemetry/instrumentation-connect": "^0.43.0", "@opentelemetry/instrumentation-connect": "^0.43.0",
"@opentelemetry/instrumentation-cucumber": "^0.13.0", "@opentelemetry/instrumentation-cucumber": "^0.14.0",
"@opentelemetry/instrumentation-dataloader": "^0.16.0", "@opentelemetry/instrumentation-dataloader": "^0.16.0",
"@opentelemetry/instrumentation-dns": "^0.43.0", "@opentelemetry/instrumentation-dns": "^0.43.0",
"@opentelemetry/instrumentation-express": "^0.47.0", "@opentelemetry/instrumentation-express": "^0.47.0",
@@ -3218,10 +3218,10 @@
"@opentelemetry/instrumentation-mongodb": "^0.51.0", "@opentelemetry/instrumentation-mongodb": "^0.51.0",
"@opentelemetry/instrumentation-mongoose": "^0.46.0", "@opentelemetry/instrumentation-mongoose": "^0.46.0",
"@opentelemetry/instrumentation-mysql": "^0.45.0", "@opentelemetry/instrumentation-mysql": "^0.45.0",
"@opentelemetry/instrumentation-mysql2": "^0.45.0", "@opentelemetry/instrumentation-mysql2": "^0.45.1",
"@opentelemetry/instrumentation-nestjs-core": "^0.44.0", "@opentelemetry/instrumentation-nestjs-core": "^0.44.0",
"@opentelemetry/instrumentation-net": "^0.43.0", "@opentelemetry/instrumentation-net": "^0.43.0",
"@opentelemetry/instrumentation-pg": "^0.50.0", "@opentelemetry/instrumentation-pg": "^0.51.0",
"@opentelemetry/instrumentation-pino": "^0.46.0", "@opentelemetry/instrumentation-pino": "^0.46.0",
"@opentelemetry/instrumentation-redis": "^0.46.0", "@opentelemetry/instrumentation-redis": "^0.46.0",
"@opentelemetry/instrumentation-redis-4": "^0.46.0", "@opentelemetry/instrumentation-redis-4": "^0.46.0",
@@ -3626,9 +3626,9 @@
} }
}, },
"node_modules/@opentelemetry/instrumentation-cucumber": { "node_modules/@opentelemetry/instrumentation-cucumber": {
"version": "0.13.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.14.0.tgz",
"integrity": "sha512-ZBswBKONU2g7mhjEKF4vkTXxezq16QdvGaD5W4o01/t5KzvCZGQ6hYPsB34miJIj/hh6UrFLRDAjqb7nur5I3Q==", "integrity": "sha512-i/GlurL1IM+CnbmItW8kx59YxAp0wu/YQkzQQRU/YGmUjym5g+/dOVjnk/K46lAU49Nn1XyFd7S3ZNf83PHL2Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/instrumentation": "^0.57.0",
@@ -3952,9 +3952,9 @@
} }
}, },
"node_modules/@opentelemetry/instrumentation-mysql2": { "node_modules/@opentelemetry/instrumentation-mysql2": {
"version": "0.45.0", "version": "0.45.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.1.tgz",
"integrity": "sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==", "integrity": "sha512-9R/vxEc02vlSqyQSmXRTvFMZVht8vgSJokKhiWA3z8Idu0mmdKFKeHiuW5yRGxM/WOi+7DWqQfYM7zw/cJc3sA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/instrumentation": "^0.57.0",
@@ -4001,14 +4001,14 @@
} }
}, },
"node_modules/@opentelemetry/instrumentation-pg": { "node_modules/@opentelemetry/instrumentation-pg": {
"version": "0.50.0", "version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.50.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.0.tgz",
"integrity": "sha512-TtLxDdYZmBhFswm8UIsrDjh/HFBeDXd4BLmE8h2MxirNHewLJ0VS9UUddKKEverb5Sm2qFVjqRjcU+8Iw4FJ3w==", "integrity": "sha512-/NStIcUWUofc11dL7tSgMk25NqvhtbHDCncgm+yc4iJF8Ste2Q/lwUitjfxqj4qWM280uFmBEtcmtMMjbjRU7Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/core": "^1.26.0", "@opentelemetry/core": "^1.26.0",
"@opentelemetry/instrumentation": "^0.57.0", "@opentelemetry/instrumentation": "^0.57.0",
"@opentelemetry/semantic-conventions": "1.27.0", "@opentelemetry/semantic-conventions": "^1.27.0",
"@opentelemetry/sql-common": "^0.40.1", "@opentelemetry/sql-common": "^0.40.1",
"@types/pg": "8.6.1", "@types/pg": "8.6.1",
"@types/pg-pool": "2.0.6" "@types/pg-pool": "2.0.6"
@@ -4020,15 +4020,6 @@
"@opentelemetry/api": "^1.3.0" "@opentelemetry/api": "^1.3.0"
} }
}, },
"node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/semantic-conventions": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz",
"integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@opentelemetry/instrumentation-pino": { "node_modules/@opentelemetry/instrumentation-pino": {
"version": "0.46.0", "version": "0.46.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.46.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.46.0.tgz",
@@ -5270,9 +5261,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.14.tgz",
"integrity": "sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==", "integrity": "sha512-WSrnE6JRnH20ZYjOOgSS4aOaPv9gxlkI2KRkN24kagbZnPZMnN8bZZyzw1rrLvwgpuRGv17Uz+hflosbR+SP6w==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -5288,16 +5279,16 @@
"url": "https://opencollective.com/swc" "url": "https://opencollective.com/swc"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-darwin-arm64": "1.10.7", "@swc/core-darwin-arm64": "1.10.14",
"@swc/core-darwin-x64": "1.10.7", "@swc/core-darwin-x64": "1.10.14",
"@swc/core-linux-arm-gnueabihf": "1.10.7", "@swc/core-linux-arm-gnueabihf": "1.10.14",
"@swc/core-linux-arm64-gnu": "1.10.7", "@swc/core-linux-arm64-gnu": "1.10.14",
"@swc/core-linux-arm64-musl": "1.10.7", "@swc/core-linux-arm64-musl": "1.10.14",
"@swc/core-linux-x64-gnu": "1.10.7", "@swc/core-linux-x64-gnu": "1.10.14",
"@swc/core-linux-x64-musl": "1.10.7", "@swc/core-linux-x64-musl": "1.10.14",
"@swc/core-win32-arm64-msvc": "1.10.7", "@swc/core-win32-arm64-msvc": "1.10.14",
"@swc/core-win32-ia32-msvc": "1.10.7", "@swc/core-win32-ia32-msvc": "1.10.14",
"@swc/core-win32-x64-msvc": "1.10.7" "@swc/core-win32-x64-msvc": "1.10.14"
}, },
"peerDependencies": { "peerDependencies": {
"@swc/helpers": "*" "@swc/helpers": "*"
@@ -5309,9 +5300,9 @@
} }
}, },
"node_modules/@swc/core-darwin-arm64": { "node_modules/@swc/core-darwin-arm64": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.14.tgz",
"integrity": "sha512-SI0OFg987P6hcyT0Dbng3YRISPS9uhLX1dzW4qRrfqQdb0i75lPJ2YWe9CN47HBazrIA5COuTzrD2Dc0TcVsSQ==", "integrity": "sha512-Dh4VyrhDDb05tdRmqJ/MucOPMTnrB4pRJol18HVyLlqu1HOT5EzonUniNTCdQbUXjgdv5UVJSTE1lYTzrp+myA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -5326,9 +5317,9 @@
} }
}, },
"node_modules/@swc/core-darwin-x64": { "node_modules/@swc/core-darwin-x64": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.14.tgz",
"integrity": "sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==", "integrity": "sha512-KpzotL/I0O12RE3tF8NmQErINv0cQe/0mnN/Q50ESFzB5kU6bLgp2HMnnwDTm/XEZZRJCNe0oc9WJ5rKbAJFRQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5343,9 +5334,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm-gnueabihf": { "node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.14.tgz",
"integrity": "sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==", "integrity": "sha512-20yRXZjMJVz1wp1TcscKiGTVXistG+saIaxOmxSNQia1Qun3hSWLL+u6+5kXbfYGr7R2N6kqSwtZbIfJI25r9Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -5360,9 +5351,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-gnu": { "node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.14.tgz",
"integrity": "sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==", "integrity": "sha512-Gy7cGrNkiMfPxQyLGxdgXPwyWzNzbHuWycJFcoKBihxZKZIW8hkPBttkGivuLC+0qOgsV2/U+S7tlvAju7FtmQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -5377,9 +5368,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-musl": { "node_modules/@swc/core-linux-arm64-musl": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.14.tgz",
"integrity": "sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==", "integrity": "sha512-+oYVqJvFw62InZ8PIy1rBACJPC2WTe4vbVb9kM1jJj2D7dKLm9acnnYIVIDsM5Wo7Uab8RvPHXVbs19IBurzuw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -5394,9 +5385,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-gnu": { "node_modules/@swc/core-linux-x64-gnu": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.14.tgz",
"integrity": "sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==", "integrity": "sha512-OmEbVEKQFLQVHwo4EJl9osmlulURy46k232Opfpn/1ji0t2KcNCci3POsnfMuoZjLkGJv8vGNJdPQxX+CP+wSA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5411,9 +5402,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-musl": { "node_modules/@swc/core-linux-x64-musl": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.14.tgz",
"integrity": "sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==", "integrity": "sha512-OZW+Icm8DMPqHbhdxplkuG8qrNnPk5i7xJOZWYi1y5bTjgGFI4nEzrsmmeHKMdQTaWwsFrm3uK1rlyQ48MmXmg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5428,9 +5419,9 @@
} }
}, },
"node_modules/@swc/core-win32-arm64-msvc": { "node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.14.tgz",
"integrity": "sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==", "integrity": "sha512-sTvc+xrDQXy3HXZFtTEClY35Efvuc3D+busYm0+rb1+Thau4HLRY9WP+sOKeGwH9/16rzfzYEqD7Ds8A9ykrHw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -5445,9 +5436,9 @@
} }
}, },
"node_modules/@swc/core-win32-ia32-msvc": { "node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.14.tgz",
"integrity": "sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==", "integrity": "sha512-j2iQ4y9GWTKtES5eMU0sDsFdYni7IxME7ejFej25Tv3Fq4B+U9tgtYWlJwh1858nIWDXelHiKcSh/UICAyVMdQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -5462,9 +5453,9 @@
} }
}, },
"node_modules/@swc/core-win32-x64-msvc": { "node_modules/@swc/core-win32-x64-msvc": {
"version": "1.10.7", "version": "1.10.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.7.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.14.tgz",
"integrity": "sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==", "integrity": "sha512-TYtWkUSMkjs0jGPeWdtWbex4B+DlQZmN/ySVLiPI+EltYCLEXsFMkVFq6aWn48dqFHggFK0UYfvDrJUR2c3Qxg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5504,13 +5495,13 @@
} }
}, },
"node_modules/@testcontainers/postgresql": { "node_modules/@testcontainers/postgresql": {
"version": "10.16.0", "version": "10.18.0",
"resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.16.0.tgz", "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.18.0.tgz",
"integrity": "sha512-zWFQI+3QxlEELRvVv27i6zlVEPNUz9zKaSh7iWmFlCdfhcyr78daS0FG8FIfdQ79VK7YXA4jv+dTYXa2SwXu/w==", "integrity": "sha512-WxkE/tBlBpoKvqDEqL3i/mL6BOBWnXb8FXKtLhEeZ3lSt0zlldkTozMmewNsKJtFTBZdv7uFwMzWyXP12t0sxQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"testcontainers": "^10.16.0" "testcontainers": "^10.18.0"
} }
}, },
"node_modules/@turf/boolean-point-in-polygon": { "node_modules/@turf/boolean-point-in-polygon": {
@@ -5777,9 +5768,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
"version": "4.17.14", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==", "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -5842,9 +5833,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.10.9", "version": "22.13.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
"integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.20.0" "undici-types": "~6.20.0"
@@ -5919,9 +5910,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.0.7", "version": "19.0.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
"integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6068,21 +6059,21 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz",
"integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/type-utils": "8.20.0", "@typescript-eslint/type-utils": "8.23.0",
"@typescript-eslint/utils": "8.20.0", "@typescript-eslint/utils": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6098,16 +6089,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz",
"integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -6123,14 +6114,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz",
"integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0" "@typescript-eslint/visitor-keys": "8.23.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6141,16 +6132,16 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz",
"integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/utils": "8.20.0", "@typescript-eslint/utils": "8.23.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6165,9 +6156,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz",
"integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -6179,20 +6170,20 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz",
"integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.20.0", "@typescript-eslint/visitor-keys": "8.23.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"ts-api-utils": "^2.0.0" "ts-api-utils": "^2.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6232,16 +6223,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz",
"integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.20.0" "@typescript-eslint/typescript-estree": "8.23.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6256,13 +6247,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.20.0", "version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz",
"integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.20.0", "@typescript-eslint/types": "8.23.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -6287,9 +6278,9 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz",
"integrity": "sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==", "integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6310,8 +6301,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "3.0.3", "@vitest/browser": "3.0.5",
"vitest": "3.0.3" "vitest": "3.0.5"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -6320,14 +6311,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
"integrity": "sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==", "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"chai": "^5.1.2", "chai": "^5.1.2",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -6336,13 +6327,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
"integrity": "sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==", "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.17" "magic-string": "^0.30.17"
}, },
@@ -6373,9 +6364,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
"integrity": "sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==", "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6386,38 +6377,38 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
"integrity": "sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==", "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"pathe": "^2.0.1" "pathe": "^2.0.2"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
"integrity": "sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==", "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.0.3", "@vitest/pretty-format": "3.0.5",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.1" "pathe": "^2.0.2"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
"integrity": "sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==", "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6428,13 +6419,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
"integrity": "sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==", "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.0.3", "@vitest/pretty-format": "3.0.5",
"loupe": "^3.1.2", "loupe": "^3.1.2",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -8836,9 +8827,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.18.0", "version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8847,7 +8838,7 @@
"@eslint/config-array": "^0.19.0", "@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.10.0", "@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.18.0", "@eslint/js": "9.19.0",
"@eslint/plugin-kit": "^0.2.5", "@eslint/plugin-kit": "^0.2.5",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -8909,9 +8900,9 @@
} }
}, },
"node_modules/eslint-plugin-prettier": { "node_modules/eslint-plugin-prettier": {
"version": "5.2.2", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
"integrity": "sha512-1yI3/hf35wmlq66C8yOyrujQnel+v5l1Vop5Cl2I6ylyNTT1JbuUUnV3/41PzwTzcyDp/oF0jWE3HXvcH5AQOQ==", "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -9861,9 +9852,9 @@
} }
}, },
"node_modules/geo-tz": { "node_modules/geo-tz": {
"version": "8.1.2", "version": "8.1.3",
"resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.1.2.tgz", "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.1.3.tgz",
"integrity": "sha512-S1udoP7MZ+CVu+7Iy/VayVNmEHTWgfJ52TjpfC2/4f+j0SB/ZXMjGrwZTqPMo6/O2m5lrGLCFCY0bkxUqiLN+g==", "integrity": "sha512-zzF0hjqLl+1n5tXDCxwdS/BmF+N1TdQc6rbubh6PO6/9DtntX/yBox1Ti0q24MrjajWG0fSv0gv2w6Zff/kmeA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@turf/boolean-point-in-polygon": "^7.1.0", "@turf/boolean-point-in-polygon": "^7.1.0",
@@ -11210,9 +11201,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/loupe": { "node_modules/loupe": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -11515,9 +11506,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/mock-fs": { "node_modules/mock-fs": {
"version": "5.4.1", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.5.0.tgz",
"integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", "integrity": "sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -11803,9 +11794,9 @@
} }
}, },
"node_modules/nestjs-cls": { "node_modules/nestjs-cls": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-5.0.1.tgz",
"integrity": "sha512-0rpkCngRISkd2x9z7q0bR8R4AeZe43WkLBaDuTE6Uaw9r1OEWfEGfGS6M4OlXG20CIxDtaAIbWW8wFM5YCNRkA==", "integrity": "sha512-TA1qkFkkWct1LXXjcNk4jxQgvZrL34bojK3D4oOo59ru7xrQ1erN3/737T+XezOZ2ZPngGVeJZ9WipaD9N+G0Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=16" "node": ">=16"
@@ -11966,9 +11957,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "6.9.16", "version": "6.10.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
"license": "MIT-0", "license": "MIT-0",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -14028,9 +14019,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.3", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -14569,9 +14560,9 @@
} }
}, },
"node_modules/sql-formatter": { "node_modules/sql-formatter": {
"version": "15.4.9", "version": "15.4.10",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.9.tgz", "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.10.tgz",
"integrity": "sha512-5vmt2HlCAVozxsBZuXWkAki/KGawaK+b5GG5x+BtXOFVpN/8cqppblFUxHl4jxdA0cvo14lABhM+KBnrUapOlw==", "integrity": "sha512-zQfiuxU1F/C7TNu+880BdL+fuvJTd1Kj8R0wv48dfZ27NR3z1PWvQFkH8ai/HrIy+NyvXCaZBkJHp/EeZFXSOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -15262,9 +15253,9 @@
} }
}, },
"node_modules/testcontainers": { "node_modules/testcontainers": {
"version": "10.16.0", "version": "10.18.0",
"resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.16.0.tgz", "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.18.0.tgz",
"integrity": "sha512-oxPLuOtrRWS11A+Yn0+zXB7GkmNarflWqmy6CQJk8KJ75LZs2/zlUXDpizTbPpCGtk4kE2EQYwFZjrE967F8Wg==", "integrity": "sha512-MnwWsPjsN5QVe+lSU1LwLZVOyjgwSwv1INzkw8FekdwgvOtvJ7FThQEkbmzRcguQootgwmA9FG54NoTChZDRvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -15282,7 +15273,7 @@
"ssh-remote-port-forward": "^1.0.4", "ssh-remote-port-forward": "^1.0.4",
"tar-fs": "^3.0.6", "tar-fs": "^3.0.6",
"tmp": "^0.2.3", "tmp": "^0.2.3",
"undici": "^5.28.4" "undici": "^5.28.5"
} }
}, },
"node_modules/testcontainers/node_modules/tmp": { "node_modules/testcontainers/node_modules/tmp": {
@@ -15456,9 +15447,9 @@
} }
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
"integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -15839,9 +15830,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "5.28.4", "version": "5.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -16078,16 +16069,16 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.3.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
"integrity": "sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==", "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cac": "^6.7.14", "cac": "^6.7.14",
"debug": "^4.4.0", "debug": "^4.4.0",
"es-module-lexer": "^1.6.0", "es-module-lexer": "^1.6.0",
"pathe": "^2.0.1", "pathe": "^2.0.2",
"vite": "^5.0.0 || ^6.0.0" "vite": "^5.0.0 || ^6.0.0"
}, },
"bin": { "bin": {
@@ -16580,31 +16571,31 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.3.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
"integrity": "sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==", "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "3.0.3", "@vitest/expect": "3.0.5",
"@vitest/mocker": "3.0.3", "@vitest/mocker": "3.0.5",
"@vitest/pretty-format": "^3.0.3", "@vitest/pretty-format": "^3.0.5",
"@vitest/runner": "3.0.3", "@vitest/runner": "3.0.5",
"@vitest/snapshot": "3.0.3", "@vitest/snapshot": "3.0.5",
"@vitest/spy": "3.0.3", "@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.3", "@vitest/utils": "3.0.5",
"chai": "^5.1.2", "chai": "^5.1.2",
"debug": "^4.4.0", "debug": "^4.4.0",
"expect-type": "^1.1.0", "expect-type": "^1.1.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.1", "pathe": "^2.0.2",
"std-env": "^3.8.0", "std-env": "^3.8.0",
"tinybench": "^2.9.0", "tinybench": "^2.9.0",
"tinyexec": "^0.3.2", "tinyexec": "^0.3.2",
"tinypool": "^1.0.2", "tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0", "tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.0.3", "vite-node": "3.0.5",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -16618,9 +16609,10 @@
}, },
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.0.3", "@vitest/browser": "3.0.5",
"@vitest/ui": "3.0.3", "@vitest/ui": "3.0.5",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@@ -16628,6 +16620,9 @@
"@edge-runtime/vm": { "@edge-runtime/vm": {
"optional": true "optional": true
}, },
"@types/debug": {
"optional": true
},
"@types/node": { "@types/node": {
"optional": true "optional": true
}, },
+2 -2
View File
@@ -45,7 +45,7 @@
"@nestjs/swagger": "^11.0.2", "@nestjs/swagger": "^11.0.2",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.0.4", "@nestjs/websockets": "^11.0.4",
"@opentelemetry/auto-instrumentations-node": "^0.55.0", "@opentelemetry/auto-instrumentations-node": "^0.56.0",
"@opentelemetry/context-async-hooks": "^1.24.0", "@opentelemetry/context-async-hooks": "^1.24.0",
"@opentelemetry/exporter-prometheus": "^0.57.0", "@opentelemetry/exporter-prometheus": "^0.57.0",
"@opentelemetry/sdk-node": "^0.57.0", "@opentelemetry/sdk-node": "^0.57.0",
@@ -112,7 +112,7 @@
"@types/lodash": "^4.14.197", "@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^22.10.9", "@types/node": "^22.13.1",
"@types/nodemailer": "^6.4.14", "@types/nodemailer": "^6.4.14",
"@types/picomatch": "^3.0.0", "@types/picomatch": "^3.0.0",
"@types/pngjs": "^6.0.5", "@types/pngjs": "^6.0.5",
+17 -7
View File
@@ -13,22 +13,23 @@ import { IWorker } from 'src/constants';
import { controllers } from 'src/controllers'; import { controllers } from 'src/controllers';
import { entities } from 'src/entities'; import { entities } from 'src/entities';
import { ImmichWorker } from 'src/enum'; import { ImmichWorker } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository } from 'src/interfaces/job.interface';
import { AuthGuard } from 'src/middleware/auth.guard'; import { AuthGuard } from 'src/middleware/auth.guard';
import { ErrorInterceptor } from 'src/middleware/error.interceptor'; import { ErrorInterceptor } from 'src/middleware/error.interceptor';
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
import { LoggingInterceptor } from 'src/middleware/logging.interceptor'; import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
import { providers, repositories } from 'src/repositories'; import { repositories } from 'src/repositories';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
import { services } from 'src/services'; import { services } from 'src/services';
import { AuthService } from 'src/services/auth.service';
import { CliService } from 'src/services/cli.service'; import { CliService } from 'src/services/cli.service';
import { DatabaseService } from 'src/services/database.service'; import { DatabaseService } from 'src/services/database.service';
const common = [...services, ...providers, ...repositories]; const common = [...repositories, ...services];
const middleware = [ const middleware = [
FileUploadInterceptor, FileUploadInterceptor,
@@ -78,21 +79,30 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
constructor( constructor(
@Inject(IWorker) private worker: ImmichWorker, @Inject(IWorker) private worker: ImmichWorker,
logger: LoggingRepository, logger: LoggingRepository,
@Inject(IEventRepository) private eventRepository: IEventRepository, private eventRepository: EventRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository, private jobRepository: JobRepository,
private telemetryRepository: TelemetryRepository, private telemetryRepository: TelemetryRepository,
private authService: AuthService,
) { ) {
logger.setAppName(this.worker); logger.setAppName(this.worker);
} }
async onModuleInit() { async onModuleInit() {
this.telemetryRepository.setup({ repositories: [...providers.map(({ useClass }) => useClass), ...repositories] }); this.telemetryRepository.setup({ repositories });
this.jobRepository.setup({ services }); this.jobRepository.setup({ services });
if (this.worker === ImmichWorker.MICROSERVICES) { if (this.worker === ImmichWorker.MICROSERVICES) {
this.jobRepository.startWorkers(); this.jobRepository.startWorkers();
} }
this.eventRepository.setAuthFn(async (client) =>
this.authService.authenticate({
headers: client.request.headers,
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' },
}),
);
this.eventRepository.setup({ services }); this.eventRepository.setup({ services });
await this.eventRepository.emit('app.bootstrap'); await this.eventRepository.emit('app.bootstrap');
} }
+9 -13
View File
@@ -4,6 +4,7 @@ import { Reflector } from '@nestjs/core';
import { SchedulerRegistry } from '@nestjs/schedule'; import { SchedulerRegistry } from '@nestjs/schedule';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ClassConstructor } from 'class-transformer';
import { PostgresJSDialect } from 'kysely-postgres-js'; import { PostgresJSDialect } from 'kysely-postgres-js';
import { KyselyModule } from 'nestjs-kysely'; import { KyselyModule } from 'nestjs-kysely';
import { OpenTelemetryModule } from 'nestjs-otel'; import { OpenTelemetryModule } from 'nestjs-otel';
@@ -13,7 +14,7 @@ import postgres from 'postgres';
import { format } from 'sql-formatter'; import { format } from 'sql-formatter';
import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
import { entities } from 'src/entities'; import { entities } from 'src/entities';
import { providers, repositories } from 'src/repositories'; import { repositories } from 'src/repositories';
import { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
@@ -45,8 +46,7 @@ export class SqlLogger implements Logger {
const reflector = new Reflector(); const reflector = new Reflector();
type Repository = (typeof providers)[0]['useClass']; type Repository = ClassConstructor<any>;
type Provider = { provide: any; useClass: Repository };
type SqlGeneratorOptions = { targetDir: string }; type SqlGeneratorOptions = { targetDir: string };
class SqlGenerator { class SqlGenerator {
@@ -59,15 +59,11 @@ class SqlGenerator {
async run() { async run() {
try { try {
await this.setup(); await this.setup();
const targets = [ for (const Repository of repositories) {
...providers, if (Repository === LoggingRepository) {
...repositories.map((repository) => ({ provide: repository, useClass: repository as any })),
];
for (const repository of targets) {
if (repository.provide === LoggingRepository) {
continue; continue;
} }
await this.process(repository); await this.process(Repository);
} }
await this.write(); await this.write();
this.stats(); this.stats();
@@ -105,19 +101,19 @@ class SqlGenerator {
TypeOrmModule.forFeature(entities), TypeOrmModule.forFeature(entities),
OpenTelemetryModule.forRoot(otel), OpenTelemetryModule.forRoot(otel),
], ],
providers: [...providers, ...repositories, AuthService, SchedulerRegistry], providers: [...repositories, AuthService, SchedulerRegistry],
}).compile(); }).compile();
this.app = await moduleFixture.createNestApplication().init(); this.app = await moduleFixture.createNestApplication().init();
} }
async process({ provide: token, useClass: Repository }: Provider) { async process(Repository: Repository) {
if (!this.app) { if (!this.app) {
throw new Error('Not initialized'); throw new Error('Not initialized');
} }
const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`]; const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`];
const instance = this.app.get<Repository>(token); const instance = this.app.get<Repository>(Repository);
// normal repositories // normal repositories
data.push(...(await this.runTargets(instance, `${Repository.name}`))); data.push(...(await this.runTargets(instance, `${Repository.name}`)));
+2 -2
View File
@@ -5,14 +5,14 @@ import {
CQMode, CQMode,
ImageFormat, ImageFormat,
LogLevel, LogLevel,
QueueName,
ToneMapping, ToneMapping,
TranscodeHWAccel, TranscodeHWAccel,
TranscodePolicy, TranscodePolicy,
VideoCodec, VideoCodec,
VideoContainer, VideoContainer,
} from 'src/enum'; } from 'src/enum';
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; import { ConcurrentQueueName, ImageOptions } from 'src/types';
import { ImageOptions } from 'src/types';
export interface SystemConfig { export interface SystemConfig {
backup: { backup: {
+11 -1
View File
@@ -1,7 +1,7 @@
import { Duration } from 'luxon'; import { Duration } from 'luxon';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { SemVer } from 'semver'; import { SemVer } from 'semver';
import { ExifOrientation } from 'src/enum'; import { DatabaseExtension, ExifOrientation } from 'src/enum';
export const POSTGRES_VERSION_RANGE = '>=14.0.0'; export const POSTGRES_VERSION_RANGE = '>=14.0.0';
export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; export const VECTORS_VERSION_RANGE = '>=0.2 <0.4';
@@ -16,6 +16,16 @@ export const LIFECYCLE_EXTENSION = 'x-immich-lifecycle';
export const DEPRECATED_IN_PREFIX = 'This property was deprecated in '; export const DEPRECATED_IN_PREFIX = 'This property was deprecated in ';
export const ADDED_IN_PREFIX = 'This property was added in '; export const ADDED_IN_PREFIX = 'This property was added in ';
export const JOBS_ASSET_PAGINATION_SIZE = 1000;
export const JOBS_LIBRARY_PAGINATION_SIZE = 10_000;
export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
cube: 'cube',
earthdistance: 'earthdistance',
vector: 'pgvector',
vectors: 'pgvecto.rs',
} as const;
export const SALT_ROUNDS = 10; export const SALT_ROUNDS = 10;
export const IWorker = 'IWorker'; export const IWorker = 'IWorker';
@@ -35,9 +35,10 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { ImmichHeader, RouteKey } from 'src/enum'; import { ImmichHeader, RouteKey } from 'src/enum';
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, UploadFiles, getFiles } from 'src/middleware/file-upload.interceptor'; import { FileUploadInterceptor, getFiles } from 'src/middleware/file-upload.interceptor';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { AssetMediaService } from 'src/services/asset-media.service'; import { AssetMediaService } from 'src/services/asset-media.service';
import { UploadFiles } from 'src/types';
import { sendFile } from 'src/utils/file'; import { sendFile } from 'src/utils/file';
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
+3 -3
View File
@@ -44,7 +44,7 @@ export class UserController {
@Get('me') @Get('me')
@Authenticated() @Authenticated()
getMyUser(@Auth() auth: AuthDto): UserAdminResponseDto { getMyUser(@Auth() auth: AuthDto): Promise<UserAdminResponseDto> {
return this.service.getMe(auth); return this.service.getMe(auth);
} }
@@ -56,7 +56,7 @@ export class UserController {
@Get('me/preferences') @Get('me/preferences')
@Authenticated() @Authenticated()
getMyPreferences(@Auth() auth: AuthDto): UserPreferencesResponseDto { getMyPreferences(@Auth() auth: AuthDto): Promise<UserPreferencesResponseDto> {
return this.service.getMyPreferences(auth); return this.service.getMyPreferences(auth);
} }
@@ -71,7 +71,7 @@ export class UserController {
@Get('me/license') @Get('me/license')
@Authenticated() @Authenticated()
getUserLicense(@Auth() auth: AuthDto): LicenseResponseDto { getUserLicense(@Auth() auth: AuthDto): Promise<LicenseResponseDto> {
return this.service.getLicense(auth); return this.service.getLicense(auth);
} }
+1
View File
@@ -5,6 +5,7 @@ vitest.mock('src/constants', () => ({
APP_MEDIA_LOCATION: '/photos', APP_MEDIA_LOCATION: '/photos',
ADDED_IN_PREFIX: 'This property was added in ', ADDED_IN_PREFIX: 'This property was added in ',
DEPRECATED_IN_PREFIX: 'This property was deprecated in ', DEPRECATED_IN_PREFIX: 'This property was deprecated in ',
IWorker: 'IWorker',
})); }));
describe('StorageCore', () => { describe('StorageCore', () => {
+24 -22
View File
@@ -4,12 +4,14 @@ import { APP_MEDIA_LOCATION } from 'src/constants';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { PersonEntity } from 'src/entities/person.entity'; import { PersonEntity } from 'src/entities/person.entity';
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum'; import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
import { IAssetRepository } from 'src/interfaces/asset.interface'; import { AssetRepository } from 'src/repositories/asset.repository';
import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ConfigRepository } from 'src/repositories/config.repository';
import { IMoveRepository } from 'src/interfaces/move.interface'; import { CryptoRepository } from 'src/repositories/crypto.repository';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { MoveRepository } from 'src/repositories/move.repository';
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { PersonRepository } from 'src/repositories/person.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { getAssetFiles } from 'src/utils/asset.util'; import { getAssetFiles } from 'src/utils/asset.util';
import { getConfig } from 'src/utils/config'; import { getConfig } from 'src/utils/config';
@@ -31,25 +33,25 @@ let instance: StorageCore | null;
export class StorageCore { export class StorageCore {
private constructor( private constructor(
private assetRepository: IAssetRepository, private assetRepository: AssetRepository,
private configRepository: IConfigRepository, private configRepository: ConfigRepository,
private cryptoRepository: ICryptoRepository, private cryptoRepository: CryptoRepository,
private moveRepository: IMoveRepository, private moveRepository: MoveRepository,
private personRepository: IPersonRepository, private personRepository: PersonRepository,
private storageRepository: IStorageRepository, private storageRepository: StorageRepository,
private systemMetadataRepository: ISystemMetadataRepository, private systemMetadataRepository: SystemMetadataRepository,
private logger: ILoggingRepository, private logger: LoggingRepository,
) {} ) {}
static create( static create(
assetRepository: IAssetRepository, assetRepository: AssetRepository,
configRepository: IConfigRepository, configRepository: ConfigRepository,
cryptoRepository: ICryptoRepository, cryptoRepository: CryptoRepository,
moveRepository: IMoveRepository, moveRepository: MoveRepository,
personRepository: IPersonRepository, personRepository: PersonRepository,
storageRepository: IStorageRepository, storageRepository: StorageRepository,
systemMetadataRepository: ISystemMetadataRepository, systemMetadataRepository: SystemMetadataRepository,
logger: ILoggingRepository, logger: LoggingRepository,
) { ) {
if (!instance) { if (!instance) {
instance = new StorageCore( instance = new StorageCore(
+50
View File
@@ -1,3 +1,53 @@
import { Permission } from 'src/enum';
export type AuthUser = {
id: string;
isAdmin: boolean;
name: string;
email: string;
quotaUsageInBytes: number;
quotaSizeInBytes: number | null;
};
export type AuthApiKey = {
id: string;
permissions: Permission[];
};
export type AuthSharedLink = {
id: string;
expiresAt: Date | null;
userId: string;
showExif: boolean;
allowUpload: boolean;
allowDownload: boolean;
password: string | null;
};
export type AuthSession = {
id: string;
};
export const columns = { export const columns = {
authUser: [
'users.id',
'users.name',
'users.email',
'users.isAdmin',
'users.quotaUsageInBytes',
'users.quotaSizeInBytes',
],
authApiKey: ['api_keys.id', 'api_keys.permissions'],
authSession: ['sessions.id', 'sessions.updatedAt'],
authSharedLink: [
'shared_links.id',
'shared_links.userId',
'shared_links.expiresAt',
'shared_links.showExif',
'shared_links.allowUpload',
'shared_links.allowDownload',
'shared_links.password',
],
userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'], userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'],
apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'],
} as const; } as const;
+14 -18
View File
@@ -3,23 +3,19 @@
* Please do not edit it manually. * Please do not edit it manually.
*/ */
import type { ColumnType } from "kysely"; import type { ColumnType } from 'kysely';
import { Permission } from 'src/enum';
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
? U[]
: ArrayTypeImpl<T>;
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S[], I[], U[]> : T[];
? ColumnType<S[], I[], U[]>
: T[];
export type AssetsStatusEnum = "active" | "deleted" | "trashed"; export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed';
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U> export type Generated<T> =
? ColumnType<S, I | undefined, U> T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
: ColumnType<T, T | undefined, T>;
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>; export type Int8 = ColumnType<number>;
export type Json = JsonValue; export type Json = JsonValue;
@@ -33,7 +29,7 @@ export type JsonPrimitive = boolean | number | string | null;
export type JsonValue = JsonArray | JsonObject | JsonPrimitive; export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
export type Sourcetype = "exif" | "machine-learning"; export type Sourcetype = 'exif' | 'machine-learning';
export type Timestamp = ColumnType<Date, Date | string, Date | string>; export type Timestamp = ColumnType<Date, Date | string, Date | string>;
@@ -81,7 +77,7 @@ export interface ApiKeys {
id: Generated<string>; id: Generated<string>;
key: string; key: string;
name: string; name: string;
permissions: string[]; permissions: Permission[];
updatedAt: Generated<Timestamp>; updatedAt: Generated<Timestamp>;
userId: string; userId: string;
} }
@@ -126,8 +122,8 @@ export interface Assets {
duplicateId: string | null; duplicateId: string | null;
duration: string | null; duration: string | null;
encodedVideoPath: Generated<string | null>; encodedVideoPath: Generated<string | null>;
fileCreatedAt: Timestamp; fileCreatedAt: Timestamp | null;
fileModifiedAt: Timestamp; fileModifiedAt: Timestamp | null;
id: Generated<string>; id: Generated<string>;
isArchived: Generated<boolean>; isArchived: Generated<boolean>;
isExternal: Generated<boolean>; isExternal: Generated<boolean>;
@@ -136,7 +132,7 @@ export interface Assets {
isVisible: Generated<boolean>; isVisible: Generated<boolean>;
libraryId: string | null; libraryId: string | null;
livePhotoVideoId: string | null; livePhotoVideoId: string | null;
localDateTime: Timestamp; localDateTime: Timestamp | null;
originalFileName: string; originalFileName: string;
originalPath: string; originalPath: string;
ownerId: string; ownerId: string;
@@ -444,6 +440,6 @@ export interface DB {
typeorm_metadata: TypeormMetadata; typeorm_metadata: TypeormMetadata;
user_metadata: UserMetadata; user_metadata: UserMetadata;
users: Users; users: Users;
"vectors.pg_vector_index_stat": VectorsPgVectorIndexStat; 'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat;
version_history: VersionHistory; version_history: VersionHistory;
} }
+2 -3
View File
@@ -2,9 +2,8 @@ import { SetMetadata, applyDecorators } from '@nestjs/common';
import { ApiExtension, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger'; import { ApiExtension, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
import _ from 'lodash'; import _ from 'lodash';
import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants'; import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants';
import { ImmichWorker, MetadataKey } from 'src/enum'; import { ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum';
import { EmitEvent } from 'src/interfaces/event.interface'; import { EmitEvent } from 'src/repositories/event.repository';
import { JobName, QueueName } from 'src/interfaces/job.interface';
import { setUnion } from 'src/utils/set'; import { setUnion } from 'src/utils/set';
// PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the // PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the
+8 -2
View File
@@ -14,8 +14,8 @@ import {
ValidateIf, ValidateIf,
} from 'class-validator'; } from 'class-validator';
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AssetType } from 'src/enum'; import { AssetType, ExifOrientation } from 'src/enum';
import { AssetStats } from 'src/interfaces/asset.interface'; import { AssetStats } from 'src/repositories/asset.repository';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
export class DeviceIdDto { export class DeviceIdDto {
@@ -54,6 +54,12 @@ export class UpdateAssetBase {
@Max(5) @Max(5)
@Min(-1) @Min(-1)
rating?: number; rating?: number;
@Optional()
@Min(1)
@Max(8)
@ApiProperty({ type: 'integer' })
orientation?: ExifOrientation;
} }
export class AssetBulkUpdateDto extends UpdateAssetBase { export class AssetBulkUpdateDto extends UpdateAssetBase {
+4 -6
View File
@@ -1,11 +1,9 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
import { SessionEntity } from 'src/entities/session.entity'; import { AuthApiKey, AuthSession, AuthSharedLink, AuthUser } from 'src/database';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
import { ImmichCookie } from 'src/enum'; import { ImmichCookie } from 'src/enum';
import { AuthApiKey } from 'src/types';
import { toEmail } from 'src/validation'; import { toEmail } from 'src/validation';
export type CookieResponse = { export type CookieResponse = {
@@ -14,11 +12,11 @@ export type CookieResponse = {
}; };
export class AuthDto { export class AuthDto {
user!: UserEntity; user!: AuthUser;
apiKey?: AuthApiKey; apiKey?: AuthApiKey;
sharedLink?: SharedLinkEntity; sharedLink?: AuthSharedLink;
session?: SessionEntity; session?: AuthSession;
} }
export class LoginCredentialDto { export class LoginCredentialDto {
+1 -2
View File
@@ -1,7 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator'; import { IsEnum, IsNotEmpty } from 'class-validator';
import { ManualJobName } from 'src/enum'; import { JobCommand, ManualJobName, QueueName } from 'src/enum';
import { JobCommand, QueueName } from 'src/interfaces/job.interface';
import { ValidateBoolean } from 'src/validation'; import { ValidateBoolean } from 'src/validation';
export class JobIdParamDto { export class JobIdParamDto {
+1 -1
View File
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator'; import { IsEnum, IsNotEmpty } from 'class-validator';
import { UserResponseDto } from 'src/dtos/user.dto'; import { UserResponseDto } from 'src/dtos/user.dto';
import { PartnerDirection } from 'src/interfaces/partner.interface'; import { PartnerDirection } from 'src/repositories/partner.repository';
export class UpdatePartnerDto { export class UpdatePartnerDto {
@IsNotEmpty() @IsNotEmpty()
+2 -1
View File
@@ -25,13 +25,14 @@ import {
Colorspace, Colorspace,
ImageFormat, ImageFormat,
LogLevel, LogLevel,
QueueName,
ToneMapping, ToneMapping,
TranscodeHWAccel, TranscodeHWAccel,
TranscodePolicy, TranscodePolicy,
VideoCodec, VideoCodec,
VideoContainer, VideoContainer,
} from 'src/enum'; } from 'src/enum';
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; import { ConcurrentQueueName } from 'src/types';
import { IsCronExpression, ValidateBoolean } from 'src/validation'; import { IsCronExpression, ValidateBoolean } from 'src/validation';
const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
+1 -1
View File
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { AssetOrder } from 'src/enum'; import { AssetOrder } from 'src/enum';
import { TimeBucketSize } from 'src/interfaces/asset.interface'; import { TimeBucketSize } from 'src/repositories/asset.repository';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
export class TimeBucketDto { export class TimeBucketDto {
+1 -1
View File
@@ -47,7 +47,7 @@ export const mapUser = (entity: UserEntity): UserResponseDto => {
email: entity.email, email: entity.email,
name: entity.name, name: entity.name,
profileImagePath: entity.profileImagePath, profileImagePath: entity.profileImagePath,
avatarColor: getPreferences(entity).avatar.color, avatarColor: getPreferences(entity.email, entity.metadata || []).avatar.color,
profileChangedAt: entity.profileChangedAt, profileChangedAt: entity.profileChangedAt,
}; };
}; };
+15 -6
View File
@@ -13,8 +13,8 @@ import { StackEntity } from 'src/entities/stack.entity';
import { TagEntity } from 'src/entities/tag.entity'; import { TagEntity } from 'src/entities/tag.entity';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
import { TimeBucketSize } from 'src/interfaces/asset.interface'; import { TimeBucketSize } from 'src/repositories/asset.repository';
import { AssetSearchBuilderOptions } from 'src/interfaces/search.interface'; import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
import { anyUuid, asUuid } from 'src/utils/database'; import { anyUuid, asUuid } from 'src/utils/database';
import { import {
Column, Column,
@@ -100,13 +100,13 @@ export class AssetEntity {
deletedAt!: Date | null; deletedAt!: Date | null;
@Index('idx_asset_file_created_at') @Index('idx_asset_file_created_at')
@Column({ type: 'timestamptz' }) @Column({ type: 'timestamptz', nullable: true, default: null })
fileCreatedAt!: Date; fileCreatedAt!: Date;
@Column({ type: 'timestamptz' }) @Column({ type: 'timestamptz', nullable: true, default: null })
localDateTime!: Date; localDateTime!: Date;
@Column({ type: 'timestamptz' }) @Column({ type: 'timestamptz', nullable: true, default: null })
fileModifiedAt!: Date; fileModifiedAt!: Date;
@Column({ type: 'boolean', default: false }) @Column({ type: 'boolean', default: false })
@@ -180,6 +180,12 @@ export class AssetEntity {
duplicateId!: string | null; duplicateId!: string | null;
} }
export type AssetEntityPlaceholder = AssetEntity & {
fileCreatedAt: Date | null;
fileModifiedAt: Date | null;
localDateTime: Date | null;
};
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) { export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')); return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
} }
@@ -419,5 +425,8 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
) )
.$if(!!options.withExif, withExifInner) .$if(!!options.withExif, withExifInner)
.$if(!!(options.withFaces || options.withPeople || options.personIds), (qb) => qb.select(withFacesAndPeople)) .$if(!!(options.withFaces || options.withPeople || options.personIds), (qb) => qb.select(withFacesAndPeople))
.$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null)); .$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null))
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null);
} }
+7 -2
View File
@@ -4,13 +4,18 @@ import { DeepPartial } from 'src/types';
import { HumanReadableSize } from 'src/utils/bytes'; import { HumanReadableSize } from 'src/utils/bytes';
import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = {
key: T;
value: UserMetadata[T];
};
@Entity('user_metadata') @Entity('user_metadata')
export class UserMetadataEntity<T extends keyof UserMetadata = UserMetadataKey> { export class UserMetadataEntity<T extends keyof UserMetadata = UserMetadataKey> implements UserMetadataItem<T> {
@PrimaryColumn({ type: 'uuid' }) @PrimaryColumn({ type: 'uuid' })
userId!: string; userId!: string;
@ManyToOne(() => UserEntity, (user) => user.metadata, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) @ManyToOne(() => UserEntity, (user) => user.metadata, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
user!: UserEntity; user?: UserEntity;
@PrimaryColumn({ type: 'varchar' }) @PrimaryColumn({ type: 'varchar' })
key!: T; key!: T;
+153
View File
@@ -384,3 +384,156 @@ export enum ExifOrientation {
MirrorHorizontalRotate90CW = 7, MirrorHorizontalRotate90CW = 7,
Rotate270CW = 8, Rotate270CW = 8,
} }
export enum DatabaseExtension {
CUBE = 'cube',
EARTH_DISTANCE = 'earthdistance',
VECTOR = 'vector',
VECTORS = 'vectors',
}
export enum BootstrapEventPriority {
// Database service should be initialized before anything else, most other services need database access
DatabaseService = -200,
// Initialise config after other bootstrap services, stop other services from using config on bootstrap
SystemConfig = 100,
}
export enum QueueName {
THUMBNAIL_GENERATION = 'thumbnailGeneration',
METADATA_EXTRACTION = 'metadataExtraction',
VIDEO_CONVERSION = 'videoConversion',
FACE_DETECTION = 'faceDetection',
FACIAL_RECOGNITION = 'facialRecognition',
SMART_SEARCH = 'smartSearch',
DUPLICATE_DETECTION = 'duplicateDetection',
BACKGROUND_TASK = 'backgroundTask',
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
MIGRATION = 'migration',
SEARCH = 'search',
SIDECAR = 'sidecar',
LIBRARY = 'library',
NOTIFICATION = 'notifications',
BACKUP_DATABASE = 'backupDatabase',
}
export enum JobName {
//backups
BACKUP_DATABASE = 'database-backup',
// conversion
QUEUE_VIDEO_CONVERSION = 'queue-video-conversion',
VIDEO_CONVERSION = 'video-conversion',
// thumbnails
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
GENERATE_THUMBNAILS = 'generate-thumbnails',
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
// metadata
QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction',
METADATA_EXTRACTION = 'metadata-extraction',
LINK_LIVE_PHOTOS = 'link-live-photos',
// user
USER_DELETION = 'user-deletion',
USER_DELETE_CHECK = 'user-delete-check',
USER_SYNC_USAGE = 'user-sync-usage',
// asset
ASSET_DELETION = 'asset-deletion',
ASSET_DELETION_CHECK = 'asset-deletion-check',
// storage template
STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration',
STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single',
// tags
TAG_CLEANUP = 'tag-cleanup',
// migration
QUEUE_MIGRATION = 'queue-migration',
MIGRATE_ASSET = 'migrate-asset',
MIGRATE_PERSON = 'migrate-person',
// facial recognition
PERSON_CLEANUP = 'person-cleanup',
QUEUE_FACE_DETECTION = 'queue-face-detection',
FACE_DETECTION = 'face-detection',
QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition',
FACIAL_RECOGNITION = 'facial-recognition',
// library management
LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files',
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets',
LIBRARY_SYNC_FILE = 'library-sync-file',
LIBRARY_SYNC_ASSET = 'library-sync-asset',
LIBRARY_DELETE = 'library-delete',
LIBRARY_QUEUE_SYNC_ALL = 'library-queue-sync-all',
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
// cleanup
DELETE_FILES = 'delete-files',
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
CLEAN_OLD_SESSION_TOKENS = 'clean-old-session-tokens',
// smart search
QUEUE_SMART_SEARCH = 'queue-smart-search',
SMART_SEARCH = 'smart-search',
QUEUE_TRASH_EMPTY = 'queue-trash-empty',
// duplicate detection
QUEUE_DUPLICATE_DETECTION = 'queue-duplicate-detection',
DUPLICATE_DETECTION = 'duplicate-detection',
// XMP sidecars
QUEUE_SIDECAR = 'queue-sidecar',
SIDECAR_DISCOVERY = 'sidecar-discovery',
SIDECAR_SYNC = 'sidecar-sync',
SIDECAR_WRITE = 'sidecar-write',
// Notification
NOTIFY_SIGNUP = 'notify-signup',
NOTIFY_ALBUM_INVITE = 'notify-album-invite',
NOTIFY_ALBUM_UPDATE = 'notify-album-update',
SEND_EMAIL = 'notification-send-email',
// Version check
VERSION_CHECK = 'version-check',
}
export enum JobCommand {
START = 'start',
PAUSE = 'pause',
RESUME = 'resume',
EMPTY = 'empty',
CLEAR_FAILED = 'clear-failed',
}
export enum JobStatus {
SUCCESS = 'success',
FAILED = 'failed',
SKIPPED = 'skipped',
}
export enum QueueCleanType {
FAILED = 'failed',
}
export enum VectorIndex {
CLIP = 'clip_index',
FACE = 'face_index',
}
export enum DatabaseLock {
GeodataImport = 100,
Migrations = 200,
SystemFileMounts = 300,
StorageTemplateMigration = 420,
VersionHistory = 500,
CLIPDimSize = 512,
Library = 1337,
GetSystemConfig = 69,
BackupDatabase = 42,
}
-35
View File
@@ -1,35 +0,0 @@
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';
export const IAlbumRepository = 'IAlbumRepository';
export interface AlbumAssetCount {
albumId: string;
assetCount: number;
startDate: Date | null;
endDate: Date | null;
}
export interface AlbumInfoOptions {
withAssets: boolean;
}
export interface IAlbumRepository extends IBulkAsset {
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | undefined>;
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]>;
removeAsset(assetId: string): Promise<void>;
getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]>;
getOwned(ownerId: string): Promise<AlbumEntity[]>;
getShared(ownerId: string): Promise<AlbumEntity[]>;
getNotShared(ownerId: string): Promise<AlbumEntity[]>;
restoreAll(userId: string): Promise<void>;
softDeleteAll(userId: string): Promise<void>;
deleteAll(userId: string): Promise<void>;
create(album: Insertable<Albums>, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise<AlbumEntity>;
update(id: string, album: Updateable<Albums>): Promise<AlbumEntity>;
delete(id: string): Promise<void>;
updateThumbnails(): Promise<number | undefined>;
}
-170
View File
@@ -1,170 +0,0 @@
import { Insertable, Updateable } from 'kysely';
import { AssetFiles, AssetJobStatus, Assets, Exif } from 'src/db';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum';
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
import { Paginated, PaginationOptions } from 'src/utils/pagination';
export type AssetStats = Record<AssetType, number>;
export interface AssetStatsOptions {
isFavorite?: boolean;
isArchived?: boolean;
isTrashed?: boolean;
}
export interface LivePhotoSearchOptions {
ownerId: string;
libraryId?: string | null;
livePhotoCID: string;
otherAssetId: string;
type: AssetType;
}
export enum WithoutProperty {
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded-video',
EXIF = 'exif',
SMART_SEARCH = 'smart-search',
DUPLICATE = 'duplicate',
FACES = 'faces',
SIDECAR = 'sidecar',
}
export enum WithProperty {
SIDECAR = 'sidecar',
}
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
export interface AssetBuilderOptions {
isArchived?: boolean;
isFavorite?: boolean;
isTrashed?: boolean;
isDuplicate?: boolean;
albumId?: string;
tagId?: string;
personId?: string;
userIds?: string[];
withStacked?: boolean;
exifInfo?: boolean;
status?: AssetStatus;
assetType?: AssetType;
}
export interface TimeBucketOptions extends AssetBuilderOptions {
size: TimeBucketSize;
order?: AssetOrder;
}
export interface TimeBucketItem {
timeBucket: string;
count: number;
}
export interface MonthDay {
day: number;
month: number;
}
export interface AssetExploreFieldOptions {
maxFields: number;
minAssetsPerField: number;
}
export interface AssetFullSyncOptions {
ownerId: string;
lastId?: string;
updatedUntil: Date;
limit: number;
}
export interface AssetDeltaSyncOptions {
userIds: string[];
updatedAfter: Date;
limit: number;
}
export interface AssetUpdateDuplicateOptions {
targetDuplicateId: string | null;
assetIds: string[];
duplicateIds: string[];
}
export interface UpsertFileOptions {
assetId: string;
type: AssetFileType;
path: string;
}
export interface AssetGetByChecksumOptions {
ownerId: string;
checksum: Buffer;
libraryId?: string;
}
export type AssetPathEntity = Pick<AssetEntity, 'id' | 'originalPath' | 'isOffline'>;
export interface GetByIdsRelations {
exifInfo?: boolean;
faces?: { person?: boolean };
files?: boolean;
library?: boolean;
owner?: boolean;
smartSearch?: boolean;
stack?: { assets?: boolean };
tags?: boolean;
}
export interface DuplicateGroup {
duplicateId: string;
assets: AssetEntity[];
}
export interface DayOfYearAssets {
yearsAgo: number;
assets: AssetEntity[];
}
export const IAssetRepository = 'IAssetRepository';
export interface IAssetRepository {
create(asset: Insertable<Assets>): Promise<AssetEntity>;
getByIds(ids: string[], relations?: GetByIdsRelations): Promise<AssetEntity[]>;
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<DayOfYearAssets[]>;
getByChecksum(options: AssetGetByChecksumOptions): Promise<AssetEntity | undefined>;
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated<AssetEntity>;
getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<string[]>;
getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated<AssetEntity>;
getById(id: string, relations?: GetByIdsRelations): Promise<AssetEntity | undefined>;
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | undefined>;
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | undefined>;
deleteAll(ownerId: string): Promise<void>;
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
getLivePhotoCount(motionId: string): Promise<number>;
updateAll(ids: string[], options: Updateable<Assets>): Promise<void>;
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
update(asset: Updateable<Assets> & { id: string }): Promise<AssetEntity>;
remove(asset: AssetEntity): Promise<void>;
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | undefined>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Insertable<Exif>): Promise<void>;
upsertJobStatus(...jobStatus: Insertable<AssetJobStatus>[]): Promise<void>;
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
getDuplicates(userId: string): Promise<DuplicateGroup[]>;
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
upsertFile(options: Insertable<AssetFiles>): Promise<void>;
upsertFiles(options: Insertable<AssetFiles>[]): Promise<void>;
}
-13
View File
@@ -1,13 +0,0 @@
export const ICryptoRepository = 'ICryptoRepository';
export interface ICryptoRepository {
randomBytes(size: number): Buffer;
randomUUID(): string;
hashFile(filePath: string | Buffer): Promise<Buffer>;
hashSha256(data: string): string;
verifySha256(data: string, encrypted: string, publicKey: string): boolean;
hashSha1(data: string | Buffer): Buffer;
hashBcrypt(data: string | Buffer, saltOrRounds: string | number): Promise<string>;
compareBcrypt(data: string | Buffer, encrypted: string): boolean;
newPassword(bytes: number): string;
}
@@ -1,78 +0,0 @@
export enum DatabaseExtension {
CUBE = 'cube',
EARTH_DISTANCE = 'earthdistance',
VECTOR = 'vector',
VECTORS = 'vectors',
}
export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS;
export type DatabaseConnectionURL = {
connectionType: 'url';
url: string;
};
export type DatabaseConnectionParts = {
connectionType: 'parts';
host: string;
port: number;
username: string;
password: string;
database: string;
};
export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts;
export enum VectorIndex {
CLIP = 'clip_index',
FACE = 'face_index',
}
export enum DatabaseLock {
GeodataImport = 100,
Migrations = 200,
SystemFileMounts = 300,
StorageTemplateMigration = 420,
VersionHistory = 500,
CLIPDimSize = 512,
Library = 1337,
GetSystemConfig = 69,
BackupDatabase = 42,
}
export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
cube: 'cube',
earthdistance: 'earthdistance',
vector: 'pgvector',
vectors: 'pgvecto.rs',
} as const;
export interface ExtensionVersion {
availableVersion: string | null;
installedVersion: string | null;
}
export interface VectorUpdateResult {
restartRequired: boolean;
}
export const IDatabaseRepository = 'IDatabaseRepository';
export interface IDatabaseRepository {
init(): void;
reconnect(): Promise<boolean>;
shutdown(): Promise<void>;
getExtensionVersion(extension: DatabaseExtension): Promise<ExtensionVersion>;
getExtensionVersionRange(extension: VectorExtension): string;
getPostgresVersion(): Promise<string>;
getPostgresVersionRange(): string;
createExtension(extension: DatabaseExtension): Promise<void>;
updateVectorExtension(extension: VectorExtension, version?: string): Promise<VectorUpdateResult>;
reindex(index: VectorIndex): Promise<void>;
shouldReindex(name: VectorIndex): Promise<boolean>;
runMigrations(options?: { transaction?: 'all' | 'none' | 'each' }): Promise<void>;
withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R>;
tryLock(lock: DatabaseLock): Promise<boolean>;
isBusy(lock: DatabaseLock): boolean;
wait(lock: DatabaseLock): Promise<void>;
}
-114
View File
@@ -1,114 +0,0 @@
import { ClassConstructor } from 'class-transformer';
import { SystemConfig } from 'src/config';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
import { JobItem, QueueName } from 'src/interfaces/job.interface';
export const IEventRepository = 'IEventRepository';
type EventMap = {
// app events
'app.bootstrap': [];
'app.shutdown': [];
'config.init': [{ newConfig: SystemConfig }];
// config events
'config.update': [
{
newConfig: SystemConfig;
oldConfig: SystemConfig;
},
];
'config.validate': [{ newConfig: SystemConfig; oldConfig: SystemConfig }];
// album events
'album.update': [{ id: string; recipientIds: string[] }];
'album.invite': [{ id: string; userId: string }];
// asset events
'asset.tag': [{ assetId: string }];
'asset.untag': [{ assetId: string }];
'asset.hide': [{ assetId: string; userId: string }];
'asset.show': [{ assetId: string; userId: string }];
'asset.trash': [{ assetId: string; userId: string }];
'asset.delete': [{ assetId: string; userId: string }];
// asset bulk events
'assets.trash': [{ assetIds: string[]; userId: string }];
'assets.delete': [{ assetIds: string[]; userId: string }];
'assets.restore': [{ assetIds: string[]; userId: string }];
'job.start': [QueueName, JobItem];
// session events
'session.delete': [{ sessionId: string }];
// stack events
'stack.create': [{ stackId: string; userId: string }];
'stack.update': [{ stackId: string; userId: string }];
'stack.delete': [{ stackId: string; userId: string }];
// stack bulk events
'stacks.delete': [{ stackIds: string[]; userId: string }];
// user events
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
// websocket events
'websocket.connect': [{ userId: string }];
};
export const serverEvents = ['config.update'] as const;
export type ServerEvents = (typeof serverEvents)[number];
export type EmitEvent = keyof EventMap;
export type EmitHandler<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<void> | void;
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
export type ArgsOf<T extends EmitEvent> = EventMap[T];
export interface ClientEventMap {
on_upload_success: [AssetResponseDto];
on_user_delete: [string];
on_asset_delete: [string];
on_asset_trash: [string[]];
on_asset_update: [AssetResponseDto];
on_asset_hidden: [string];
on_asset_restore: [string[]];
on_asset_stack_update: string[];
on_person_thumbnail: [string];
on_server_version: [ServerVersionResponseDto];
on_config_update: [];
on_new_release: [ReleaseNotification];
on_session_delete: [string];
}
export type EventItem<T extends EmitEvent> = {
event: T;
handler: EmitHandler<T>;
server: boolean;
};
export enum BootstrapEventPriority {
// Database service should be initialized before anything else, most other services need database access
DatabaseService = -200,
// Initialise config after other bootstrap services, stop other services from using config on bootstrap
SystemConfig = 100,
}
export interface IEventRepository {
setup(options: { services: ClassConstructor<unknown>[] }): void;
emit<T extends keyof EventMap>(event: T, ...args: ArgsOf<T>): Promise<void>;
/**
* Send to connected clients for a specific user
*/
clientSend<E extends keyof ClientEventMap>(event: E, room: string, ...data: ClientEventMap[E]): void;
/**
* Send to all connected clients
*/
clientBroadcast<E extends keyof ClientEventMap>(event: E, ...data: ClientEventMap[E]): void;
/**
* Send to all connected servers
*/
serverSend<T extends ServerEvents>(event: T, ...args: ArgsOf<T>): void;
}
-329
View File
@@ -1,329 +0,0 @@
import { ClassConstructor } from 'class-transformer';
import { EmailImageAttachment } from 'src/repositories/notification.repository';
export enum QueueName {
THUMBNAIL_GENERATION = 'thumbnailGeneration',
METADATA_EXTRACTION = 'metadataExtraction',
VIDEO_CONVERSION = 'videoConversion',
FACE_DETECTION = 'faceDetection',
FACIAL_RECOGNITION = 'facialRecognition',
SMART_SEARCH = 'smartSearch',
DUPLICATE_DETECTION = 'duplicateDetection',
BACKGROUND_TASK = 'backgroundTask',
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
MIGRATION = 'migration',
SEARCH = 'search',
SIDECAR = 'sidecar',
LIBRARY = 'library',
NOTIFICATION = 'notifications',
BACKUP_DATABASE = 'backupDatabase',
}
export type ConcurrentQueueName = Exclude<
QueueName,
| QueueName.STORAGE_TEMPLATE_MIGRATION
| QueueName.FACIAL_RECOGNITION
| QueueName.DUPLICATE_DETECTION
| QueueName.BACKUP_DATABASE
>;
export enum JobCommand {
START = 'start',
PAUSE = 'pause',
RESUME = 'resume',
EMPTY = 'empty',
CLEAR_FAILED = 'clear-failed',
}
export enum JobName {
//backups
BACKUP_DATABASE = 'database-backup',
// conversion
QUEUE_VIDEO_CONVERSION = 'queue-video-conversion',
VIDEO_CONVERSION = 'video-conversion',
// thumbnails
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
GENERATE_THUMBNAILS = 'generate-thumbnails',
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
// metadata
QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction',
METADATA_EXTRACTION = 'metadata-extraction',
LINK_LIVE_PHOTOS = 'link-live-photos',
// user
USER_DELETION = 'user-deletion',
USER_DELETE_CHECK = 'user-delete-check',
USER_SYNC_USAGE = 'user-sync-usage',
// asset
ASSET_DELETION = 'asset-deletion',
ASSET_DELETION_CHECK = 'asset-deletion-check',
// storage template
STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration',
STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single',
// tags
TAG_CLEANUP = 'tag-cleanup',
// migration
QUEUE_MIGRATION = 'queue-migration',
MIGRATE_ASSET = 'migrate-asset',
MIGRATE_PERSON = 'migrate-person',
// facial recognition
PERSON_CLEANUP = 'person-cleanup',
QUEUE_FACE_DETECTION = 'queue-face-detection',
FACE_DETECTION = 'face-detection',
QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition',
FACIAL_RECOGNITION = 'facial-recognition',
// library management
LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files',
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets',
LIBRARY_SYNC_FILE = 'library-sync-file',
LIBRARY_SYNC_ASSET = 'library-sync-asset',
LIBRARY_DELETE = 'library-delete',
LIBRARY_QUEUE_SYNC_ALL = 'library-queue-sync-all',
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
// cleanup
DELETE_FILES = 'delete-files',
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
CLEAN_OLD_SESSION_TOKENS = 'clean-old-session-tokens',
// smart search
QUEUE_SMART_SEARCH = 'queue-smart-search',
SMART_SEARCH = 'smart-search',
QUEUE_TRASH_EMPTY = 'queue-trash-empty',
// duplicate detection
QUEUE_DUPLICATE_DETECTION = 'queue-duplicate-detection',
DUPLICATE_DETECTION = 'duplicate-detection',
// XMP sidecars
QUEUE_SIDECAR = 'queue-sidecar',
SIDECAR_DISCOVERY = 'sidecar-discovery',
SIDECAR_SYNC = 'sidecar-sync',
SIDECAR_WRITE = 'sidecar-write',
// Notification
NOTIFY_SIGNUP = 'notify-signup',
NOTIFY_ALBUM_INVITE = 'notify-album-invite',
NOTIFY_ALBUM_UPDATE = 'notify-album-update',
SEND_EMAIL = 'notification-send-email',
// Version check
VERSION_CHECK = 'version-check',
}
export const JOBS_ASSET_PAGINATION_SIZE = 1000;
export const JOBS_LIBRARY_PAGINATION_SIZE = 10_000;
export interface IBaseJob {
force?: boolean;
}
export interface IDelayedJob extends IBaseJob {
/** The minimum time to wait to execute this job, in milliseconds. */
delay?: number;
}
export interface IEntityJob extends IBaseJob {
id: string;
source?: 'upload' | 'sidecar-write' | 'copy';
notify?: boolean;
}
export interface IAssetDeleteJob extends IEntityJob {
deleteOnDisk: boolean;
}
export interface ILibraryFileJob extends IEntityJob {
ownerId: string;
assetPath: string;
}
export interface ILibraryAssetJob extends IEntityJob {
importPaths: string[];
exclusionPatterns: string[];
}
export interface IBulkEntityJob extends IBaseJob {
ids: string[];
}
export interface IDeleteFilesJob extends IBaseJob {
files: Array<string | null | undefined>;
}
export interface ISidecarWriteJob extends IEntityJob {
description?: string;
dateTimeOriginal?: string;
latitude?: number;
longitude?: number;
rating?: number;
tags?: true;
}
export interface IDeferrableJob extends IEntityJob {
deferred?: boolean;
}
export interface INightlyJob extends IBaseJob {
nightly?: boolean;
}
export interface IEmailJob {
to: string;
subject: string;
html: string;
text: string;
imageAttachments?: EmailImageAttachment[];
}
export interface INotifySignupJob extends IEntityJob {
tempPassword?: string;
}
export interface INotifyAlbumInviteJob extends IEntityJob {
recipientId: string;
}
export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob {
recipientIds: string[];
}
export interface JobCounts {
active: number;
completed: number;
failed: number;
delayed: number;
waiting: number;
paused: number;
}
export interface QueueStatus {
isActive: boolean;
isPaused: boolean;
}
export enum QueueCleanType {
FAILED = 'failed',
}
export type JobItem =
// Backups
| { name: JobName.BACKUP_DATABASE; data?: IBaseJob }
// Transcoding
| { name: JobName.QUEUE_VIDEO_CONVERSION; data: IBaseJob }
| { name: JobName.VIDEO_CONVERSION; data: IEntityJob }
// Thumbnails
| { name: JobName.QUEUE_GENERATE_THUMBNAILS; data: IBaseJob }
| { name: JobName.GENERATE_THUMBNAILS; data: IEntityJob }
// User
| { name: JobName.USER_DELETE_CHECK; data?: IBaseJob }
| { name: JobName.USER_DELETION; data: IEntityJob }
| { name: JobName.USER_SYNC_USAGE; data?: IBaseJob }
// Storage Template
| { name: JobName.STORAGE_TEMPLATE_MIGRATION; data?: IBaseJob }
| { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE; data: IEntityJob }
// Migration
| { name: JobName.QUEUE_MIGRATION; data?: IBaseJob }
| { name: JobName.MIGRATE_ASSET; data: IEntityJob }
| { name: JobName.MIGRATE_PERSON; data: IEntityJob }
// Metadata Extraction
| { name: JobName.QUEUE_METADATA_EXTRACTION; data: IBaseJob }
| { name: JobName.METADATA_EXTRACTION; data: IEntityJob }
| { name: JobName.LINK_LIVE_PHOTOS; data: IEntityJob }
// Sidecar Scanning
| { name: JobName.QUEUE_SIDECAR; data: IBaseJob }
| { name: JobName.SIDECAR_DISCOVERY; data: IEntityJob }
| { name: JobName.SIDECAR_SYNC; data: IEntityJob }
| { name: JobName.SIDECAR_WRITE; data: ISidecarWriteJob }
// Facial Recognition
| { name: JobName.QUEUE_FACE_DETECTION; data: IBaseJob }
| { name: JobName.FACE_DETECTION; data: IEntityJob }
| { name: JobName.QUEUE_FACIAL_RECOGNITION; data: INightlyJob }
| { name: JobName.FACIAL_RECOGNITION; data: IDeferrableJob }
| { name: JobName.GENERATE_PERSON_THUMBNAIL; data: IEntityJob }
// Smart Search
| { name: JobName.QUEUE_SMART_SEARCH; data: IBaseJob }
| { name: JobName.SMART_SEARCH; data: IEntityJob }
| { name: JobName.QUEUE_TRASH_EMPTY; data?: IBaseJob }
// Duplicate Detection
| { name: JobName.QUEUE_DUPLICATE_DETECTION; data: IBaseJob }
| { name: JobName.DUPLICATE_DETECTION; data: IEntityJob }
// Filesystem
| { name: JobName.DELETE_FILES; data: IDeleteFilesJob }
// Cleanup
| { name: JobName.CLEAN_OLD_AUDIT_LOGS; data?: IBaseJob }
| { name: JobName.CLEAN_OLD_SESSION_TOKENS; data?: IBaseJob }
// Tags
| { name: JobName.TAG_CLEANUP; data?: IBaseJob }
// Asset Deletion
| { name: JobName.PERSON_CLEANUP; data?: IBaseJob }
| { name: JobName.ASSET_DELETION; data: IAssetDeleteJob }
| { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob }
// Library Management
| { name: JobName.LIBRARY_SYNC_FILE; data: ILibraryFileJob }
| { name: JobName.LIBRARY_QUEUE_SYNC_FILES; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_SYNC_ASSETS; data: IEntityJob }
| { name: JobName.LIBRARY_SYNC_ASSET; data: ILibraryAssetJob }
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
| { name: JobName.LIBRARY_QUEUE_SYNC_ALL; data?: IBaseJob }
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
// Notification
| { name: JobName.SEND_EMAIL; data: IEmailJob }
| { name: JobName.NOTIFY_ALBUM_INVITE; data: INotifyAlbumInviteJob }
| { name: JobName.NOTIFY_ALBUM_UPDATE; data: INotifyAlbumUpdateJob }
| { name: JobName.NOTIFY_SIGNUP; data: INotifySignupJob }
// Version check
| { name: JobName.VERSION_CHECK; data: IBaseJob };
export enum JobStatus {
SUCCESS = 'success',
FAILED = 'failed',
SKIPPED = 'skipped',
}
export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] };
export type JobOf<T extends JobName> = Jobs[T];
export const IJobRepository = 'IJobRepository';
export interface IJobRepository {
setup(options: { services: ClassConstructor<unknown>[] }): void;
startWorkers(): void;
run(job: JobItem): Promise<JobStatus>;
setConcurrency(queueName: QueueName, concurrency: number): void;
queue(item: JobItem): Promise<void>;
queueAll(items: JobItem[]): Promise<void>;
pause(name: QueueName): Promise<void>;
resume(name: QueueName): Promise<void>;
empty(name: QueueName): Promise<void>;
clear(name: QueueName, type: QueueCleanType): Promise<string[]>;
getQueueStatus(name: QueueName): Promise<QueueStatus>;
getJobCounts(name: QueueName): Promise<JobCounts>;
waitForQueueCompletion(...queues: QueueName[]): Promise<void>;
removeJob(jobId: string, name: JobName): Promise<IEntityJob | undefined>;
}
@@ -1,17 +0,0 @@
import { Insertable, Updateable } from 'kysely';
import { Libraries } from 'src/db';
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
import { LibraryEntity } from 'src/entities/library.entity';
export const ILibraryRepository = 'ILibraryRepository';
export interface ILibraryRepository {
getAll(withDeleted?: boolean): Promise<LibraryEntity[]>;
getAllDeleted(): Promise<LibraryEntity[]>;
get(id: string, withDeleted?: boolean): Promise<LibraryEntity | undefined>;
create(library: Insertable<Libraries>): Promise<LibraryEntity>;
delete(id: string): Promise<void>;
softDelete(id: string): Promise<void>;
update(id: string, library: Updateable<Libraries>): Promise<LibraryEntity>;
getStatistics(id: string): Promise<LibraryStatsResponseDto | undefined>;
}
@@ -1,57 +0,0 @@
export const IMachineLearningRepository = 'IMachineLearningRepository';
export interface BoundingBox {
x1: number;
y1: number;
x2: number;
y2: number;
}
export enum ModelTask {
FACIAL_RECOGNITION = 'facial-recognition',
SEARCH = 'clip',
}
export enum ModelType {
DETECTION = 'detection',
PIPELINE = 'pipeline',
RECOGNITION = 'recognition',
TEXTUAL = 'textual',
VISUAL = 'visual',
}
export type ModelPayload = { imagePath: string } | { text: string };
type ModelOptions = { modelName: string };
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]: string } & VisualResponse;
export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } };
export type ClipTextualResponse = { [ModelTask.SEARCH]: string };
export type FacialRecognitionRequest = {
[ModelTask.FACIAL_RECOGNITION]: {
[ModelType.DETECTION]: ModelOptions & { options: { minScore: number } };
[ModelType.RECOGNITION]: ModelOptions;
};
};
export interface Face {
boundingBox: BoundingBox;
embedding: string;
score: number;
}
export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse;
export type DetectedFaces = { faces: Face[] } & VisualResponse;
export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest;
export interface IMachineLearningRepository {
encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise<string>;
encodeText(urls: string[], text: string, config: ModelOptions): Promise<string>;
detectFaces(urls: string[], imagePath: string, config: FaceDetectionOptions): Promise<DetectedFaces>;
}
-15
View File
@@ -1,15 +0,0 @@
import { Insertable, Updateable } from 'kysely';
import { MoveHistory } from 'src/db';
import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum';
export const IMoveRepository = 'IMoveRepository';
export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
export interface IMoveRepository {
create(entity: Insertable<MoveHistory>): Promise<MoveEntity>;
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | undefined>;
update(id: string, entity: Updateable<MoveHistory>): Promise<MoveEntity>;
delete(id: string): Promise<MoveEntity>;
}
@@ -1,23 +0,0 @@
import { Updateable } from 'kysely';
import { Partners } from 'src/db';
import { PartnerEntity } from 'src/entities/partner.entity';
export interface PartnerIds {
sharedById: string;
sharedWithId: string;
}
export enum PartnerDirection {
SharedBy = 'shared-by',
SharedWith = 'shared-with',
}
export const IPartnerRepository = 'IPartnerRepository';
export interface IPartnerRepository {
getAll(userId: string): Promise<PartnerEntity[]>;
get(partner: PartnerIds): Promise<PartnerEntity | undefined>;
create(partner: PartnerIds): Promise<PartnerEntity>;
remove(partner: PartnerIds): Promise<void>;
update(partner: PartnerIds, entity: Updateable<Partners>): Promise<PartnerEntity>;
}
-89
View File
@@ -1,89 +0,0 @@
import { Insertable, Selectable, Updateable } from 'kysely';
import { AssetFaces, FaceSearch, Person } from 'src/db';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum';
import { Paginated, PaginationOptions } from 'src/utils/pagination';
import { FindOptionsRelations } from 'typeorm';
export const IPersonRepository = 'IPersonRepository';
export interface PersonSearchOptions {
minimumFaceCount: number;
withHidden: boolean;
closestFaceAssetId?: string;
}
export interface PersonNameSearchOptions {
withHidden?: boolean;
}
export interface PersonNameResponse {
id: string;
name: string;
}
export interface AssetFaceId {
assetId: string;
personId: string;
}
export interface UpdateFacesData {
oldPersonId?: string;
faceIds?: string[];
newPersonId: string;
}
export interface PersonStatistics {
assets: number;
}
export interface PeopleStatistics {
total: number;
hidden: number;
}
export interface DeleteFacesOptions {
sourceType: SourceType;
}
export type UnassignFacesOptions = DeleteFacesOptions;
export type SelectFaceOptions = (keyof Selectable<AssetFaces>)[];
export interface IPersonRepository {
getAll(options?: Partial<PersonEntity>): AsyncIterableIterator<PersonEntity>;
getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions): Paginated<PersonEntity>;
getAllWithoutFaces(): Promise<PersonEntity[]>;
getById(personId: string): Promise<PersonEntity | null>;
getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise<PersonEntity[]>;
getDistinctNames(userId: string, options: PersonNameSearchOptions): Promise<PersonNameResponse[]>;
create(person: Insertable<Person>): Promise<PersonEntity>;
createAll(people: Insertable<Person>[]): Promise<string[]>;
delete(entities: PersonEntity[]): Promise<void>;
deleteFaces(options: DeleteFacesOptions): Promise<void>;
refreshFaces(
facesToAdd: Insertable<AssetFaces>[],
faceIdsToRemove: string[],
embeddingsToAdd?: Insertable<FaceSearch>[],
): Promise<void>;
getAllFaces(options?: Partial<AssetFaceEntity>): AsyncIterableIterator<AssetFaceEntity>;
getFaceById(id: string): Promise<AssetFaceEntity>;
getFaceByIdWithAssets(
id: string,
relations?: FindOptionsRelations<AssetFaceEntity>,
select?: SelectFaceOptions,
): Promise<AssetFaceEntity | undefined>;
getFaces(assetId: string): Promise<AssetFaceEntity[]>;
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
getRandomFace(personId: string): Promise<AssetFaceEntity | undefined>;
getStatistics(personId: string): Promise<PersonStatistics>;
reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
getNumberOfPeople(userId: string): Promise<PeopleStatistics>;
reassignFaces(data: UpdateFacesData): Promise<number>;
unassignFaces(options: UnassignFacesOptions): Promise<void>;
update(person: Updateable<Person> & { id: string }): Promise<PersonEntity>;
updateAll(people: Insertable<Person>[]): Promise<void>;
getLatestFaceDate(): Promise<string | undefined>;
}
-213
View File
@@ -1,213 +0,0 @@
import { AssetEntity } from 'src/entities/asset.entity';
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
import { AssetStatus, AssetType } from 'src/enum';
import { Paginated } from 'src/utils/pagination';
export const ISearchRepository = 'ISearchRepository';
export interface SearchResult<T> {
/** total matches */
total: number;
/** collection size */
count: number;
/** current page */
page: number;
/** items for page */
items: T[];
/** score */
distances: number[];
facets: SearchFacet[];
}
export interface SearchFacet {
fieldName: string;
counts: Array<{
count: number;
value: string;
}>;
}
export type SearchExploreItemSet<T> = Array<{
value: string;
data: T;
}>;
export interface SearchExploreItem<T> {
fieldName: string;
items: SearchExploreItemSet<T>;
}
export interface SearchAssetIDOptions {
checksum?: Buffer;
deviceAssetId?: string;
id?: string;
}
export interface SearchUserIdOptions {
deviceId?: string;
libraryId?: string | null;
userIds?: string[];
}
export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions;
export interface SearchStatusOptions {
isArchived?: boolean;
isEncoded?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isVisible?: boolean;
isNotInAlbum?: boolean;
type?: AssetType;
status?: AssetStatus;
withArchived?: boolean;
withDeleted?: boolean;
}
export interface SearchOneToOneRelationOptions {
withExif?: boolean;
withStacked?: boolean;
}
export interface SearchRelationOptions extends SearchOneToOneRelationOptions {
withFaces?: boolean;
withPeople?: boolean;
}
export interface SearchDateOptions {
createdBefore?: Date;
createdAfter?: Date;
takenBefore?: Date;
takenAfter?: Date;
trashedBefore?: Date;
trashedAfter?: Date;
updatedBefore?: Date;
updatedAfter?: Date;
}
export interface SearchPathOptions {
encodedVideoPath?: string;
originalFileName?: string;
originalPath?: string;
previewPath?: string;
thumbnailPath?: string;
}
export interface SearchExifOptions {
city?: string | null;
country?: string | null;
lensModel?: string | null;
make?: string | null;
model?: string | null;
state?: string | null;
description?: string | null;
}
export interface SearchEmbeddingOptions {
embedding: string;
userIds: string[];
}
export interface SearchPeopleOptions {
personIds?: string[];
}
export interface SearchTagOptions {
tagIds?: string[];
}
export interface SearchOrderOptions {
orderDirection?: 'asc' | 'desc';
}
export interface SearchPaginationOptions {
page: number;
size: number;
}
type BaseAssetSearchOptions = SearchDateOptions &
SearchIdOptions &
SearchExifOptions &
SearchOrderOptions &
SearchPathOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions &
SearchTagOptions;
export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions;
export type AssetSearchOneToOneRelationOptions = BaseAssetSearchOptions & SearchOneToOneRelationOptions;
export type AssetSearchBuilderOptions = Omit<AssetSearchOptions, 'orderDirection'>;
export type SmartSearchOptions = SearchDateOptions &
SearchEmbeddingOptions &
SearchExifOptions &
SearchOneToOneRelationOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions &
SearchTagOptions;
export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
hasPerson?: boolean;
numResults: number;
maxDistance: number;
}
export interface AssetDuplicateSearch {
assetId: string;
embedding: string;
maxDistance: number;
type: AssetType;
userIds: string[];
}
export interface FaceSearchResult {
distance: number;
id: string;
personId: string | null;
}
export interface AssetDuplicateResult {
assetId: string;
duplicateId: string | null;
distance: number;
}
export interface GetStatesOptions {
country?: string;
}
export interface GetCitiesOptions extends GetStatesOptions {
state?: string;
}
export interface GetCameraModelsOptions {
make?: string;
}
export interface GetCameraMakesOptions {
model?: string;
}
export interface ISearchRepository {
searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions): Paginated<AssetEntity>;
searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions): Paginated<AssetEntity>;
searchDuplicates(options: AssetDuplicateSearch): Promise<AssetDuplicateResult[]>;
searchFaces(search: FaceEmbeddingSearch): Promise<FaceSearchResult[]>;
searchRandom(size: number, options: AssetSearchOptions): Promise<AssetEntity[]>;
upsert(assetId: string, embedding: string): Promise<void>;
searchPlaces(placeName: string): Promise<GeodataPlacesEntity[]>;
getAssetsByCity(userIds: string[]): Promise<AssetEntity[]>;
deleteAllSearchEmbeddings(): Promise<void>;
getDimensionSize(): Promise<number>;
setDimensionSize(dimSize: number): Promise<void>;
getCountries(userIds: string[]): Promise<Array<string | null>>;
getStates(userIds: string[], options: GetStatesOptions): Promise<Array<string | null>>;
getCities(userIds: string[], options: GetCitiesOptions): Promise<Array<string | null>>;
getCameraMakes(userIds: string[], options: GetCameraMakesOptions): Promise<Array<string | null>>;
getCameraModels(userIds: string[], options: GetCameraModelsOptions): Promise<Array<string | null>>;
}
@@ -1,19 +0,0 @@
import { Insertable, Updateable } from 'kysely';
import { SharedLinks } from 'src/db';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
export const ISharedLinkRepository = 'ISharedLinkRepository';
export type SharedLinkSearchOptions = {
userId: string;
albumId?: string;
};
export interface ISharedLinkRepository {
getAll(options: SharedLinkSearchOptions): Promise<SharedLinkEntity[]>;
get(userId: string, id: string): Promise<SharedLinkEntity | undefined>;
getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>;
create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>;
update(entity: Updateable<SharedLinks> & { id: string; assetIds?: string[] }): Promise<SharedLinkEntity>;
remove(entity: SharedLinkEntity): Promise<void>;
}
-18
View File
@@ -1,18 +0,0 @@
import { Updateable } from 'kysely';
import { StackEntity } from 'src/entities/stack.entity';
export const IStackRepository = 'IStackRepository';
export interface StackSearch {
ownerId: string;
primaryAssetId?: string;
}
export interface IStackRepository {
search(query: StackSearch): Promise<StackEntity[]>;
create(stack: { ownerId: string; assetIds: string[] }): Promise<StackEntity>;
update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity>;
delete(id: string): Promise<void>;
deleteAll(ids: string[]): Promise<void>;
getById(id: string): Promise<StackEntity | undefined>;
}
@@ -1,57 +0,0 @@
import { WatchOptions } from 'chokidar';
import { Stats } from 'node:fs';
import { FileReadOptions } from 'node:fs/promises';
import { Readable, Writable } from 'node:stream';
import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto';
export interface ImmichReadStream {
stream: Readable;
type?: string;
length?: number;
}
export interface ImmichZipStream extends ImmichReadStream {
addFile: (inputPath: string, filename: string) => void;
finalize: () => Promise<void>;
}
export interface DiskUsage {
available: number;
free: number;
total: number;
}
export const IStorageRepository = 'IStorageRepository';
export interface WatchEvents {
onReady(): void;
onAdd(path: string): void;
onChange(path: string): void;
onUnlink(path: string): void;
onError(error: Error): void;
}
export interface IStorageRepository {
createZipStream(): ImmichZipStream;
createReadStream(filepath: string, mimeType?: string | null): Promise<ImmichReadStream>;
readFile(filepath: string, options?: FileReadOptions<Buffer>): Promise<Buffer>;
createFile(filepath: string, buffer: Buffer): Promise<void>;
createWriteStream(filepath: string): Writable;
createOrOverwriteFile(filepath: string, buffer: Buffer): Promise<void>;
overwriteFile(filepath: string, buffer: Buffer): Promise<void>;
realpath(filepath: string): Promise<string>;
unlink(filepath: string): Promise<void>;
unlinkDir(folder: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
removeEmptyDirs(folder: string, self?: boolean): Promise<void>;
checkFileExists(filepath: string, mode?: number): Promise<boolean>;
mkdirSync(filepath: string): void;
checkDiskUsage(folder: string): Promise<DiskUsage>;
readdir(folder: string): Promise<string[]>;
stat(filepath: string): Promise<Stats>;
crawl(options: CrawlOptionsDto): Promise<string[]>;
walk(options: WalkOptionsDto): AsyncGenerator<string[]>;
copyFile(source: string, target: string): Promise<void>;
rename(source: string, target: string): Promise<void>;
watch(paths: string[], options: WatchOptions, events: Partial<WatchEvents>): () => Promise<void>;
utimes(filepath: string, atime: Date, mtime: Date): Promise<void>;
}
-21
View File
@@ -1,21 +0,0 @@
import { TagEntity } from 'src/entities/tag.entity';
import { IBulkAsset } from 'src/utils/asset.util';
export const ITagRepository = 'ITagRepository';
export type AssetTagItem = { assetId: string; tagId: string };
export interface ITagRepository extends IBulkAsset {
getAll(userId: string): Promise<TagEntity[]>;
getByValue(userId: string, value: string): Promise<TagEntity | null>;
upsertValue(request: { userId: string; value: string; parent?: TagEntity }): Promise<TagEntity>;
create(tag: Partial<TagEntity>): Promise<TagEntity>;
get(id: string): Promise<TagEntity | null>;
update(tag: { id: string } & Partial<TagEntity>): Promise<TagEntity>;
delete(id: string): Promise<void>;
upsertAssetTags({ assetId, tagIds }: { assetId: string; tagIds: string[] }): Promise<void>;
upsertAssetIds(items: AssetTagItem[]): Promise<AssetTagItem[]>;
deleteEmptyTags(): Promise<void>;
}
-45
View File
@@ -1,45 +0,0 @@
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';
export interface UserListFilter {
withDeleted?: boolean;
}
export interface UserStatsQueryResponse {
userId: string;
userName: string;
photos: number;
videos: number;
usage: number;
usagePhotos: number;
usageVideos: number;
quotaSizeInBytes: number | null;
}
export interface UserFindOptions {
withDeleted?: boolean;
}
export const IUserRepository = 'IUserRepository';
export interface IUserRepository {
get(id: string, options: UserFindOptions): Promise<UserEntity | undefined>;
getAdmin(): Promise<UserEntity | undefined>;
hasAdmin(): Promise<boolean>;
getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | undefined>;
getByStorageLabel(storageLabel: string): Promise<UserEntity | undefined>;
getByOAuthId(oauthId: string): Promise<UserEntity | undefined>;
getDeletedUsers(): Promise<UserEntity[]>;
getList(filter?: UserListFilter): Promise<UserEntity[]>;
getUserStats(): Promise<UserStatsQueryResponse[]>;
create(user: Insertable<Users>): Promise<UserEntity>;
update(id: string, user: Updateable<Users>): Promise<UserEntity>;
restore(id: string): Promise<UserEntity>;
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
deleteMetadata<T extends keyof UserMetadata>(id: string, key: T): Promise<void>;
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
updateUsage(id: string, delta: number): Promise<void>;
syncUsage(id?: string): Promise<void>;
}
@@ -10,14 +10,10 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto';
import { RouteKey } from 'src/enum'; import { RouteKey } from 'src/enum';
import { AuthRequest } from 'src/middleware/auth.guard'; import { AuthRequest } from 'src/middleware/auth.guard';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { AssetMediaService, UploadFile } from 'src/services/asset-media.service'; import { AssetMediaService } from 'src/services/asset-media.service';
import { ImmichFile, UploadFile, UploadFiles } from 'src/types';
import { asRequest, mapToUploadFile } from 'src/utils/asset.util'; import { asRequest, mapToUploadFile } from 'src/utils/asset.util';
export interface UploadFiles {
assetData: ImmichFile[];
sidecarData: ImmichFile[];
}
export function getFile(files: UploadFiles, property: 'assetData' | 'sidecarData') { export function getFile(files: UploadFiles, property: 'assetData' | 'sidecarData') {
const file = files[property]?.[0]; const file = files[property]?.[0];
return file ? mapToUploadFile(file) : file; return file ? mapToUploadFile(file) : file;
@@ -30,12 +26,6 @@ export function getFiles(files: UploadFiles) {
}; };
} }
export interface ImmichFile extends Express.Multer.File {
/** sha1 hash of file */
uuid: string;
checksum: Buffer;
}
type DiskStorageCallback = (error: Error | null, result: string) => void; type DiskStorageCallback = (error: Error | null, result: string) => void;
type ImmichMulterFile = Express.Multer.File & { uuid: string }; type ImmichMulterFile = Express.Multer.File & { uuid: string };
@@ -1,4 +1,4 @@
import { DatabaseExtension } from 'src/interfaces/database.interface'; import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
@@ -1,4 +1,4 @@
import { DatabaseExtension } from 'src/interfaces/database.interface'; import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
@@ -1,4 +1,4 @@
import { DatabaseExtension } from 'src/interfaces/database.interface'; import { DatabaseExtension } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { MigrationInterface, QueryRunner } from 'typeorm'; import { MigrationInterface, QueryRunner } from 'typeorm';
@@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NullableDates1737845696644 implements MigrationInterface {
name = 'NullableDates1737845696644'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileCreatedAt" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileModifiedAt" DROP NOT NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileModifiedAt" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "fileCreatedAt" SET NOT NULL`);
}
}
@@ -43,3 +43,6 @@ where
and "activity"."albumId" = $2 and "activity"."albumId" = $2
and "activity"."isLiked" = $3 and "activity"."isLiked" = $3
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
+16 -17
View File
@@ -3,29 +3,28 @@
-- ApiKeyRepository.getKey -- ApiKeyRepository.getKey
select select
"api_keys"."id", "api_keys"."id",
"api_keys"."key",
"api_keys"."userId",
"api_keys"."permissions", "api_keys"."permissions",
to_json("user") as "user" (
from
"api_keys"
inner join lateral (
select select
"users".*, to_json(obj)
from
( (
select select
array_agg("user_metadata") as "metadata" "users"."id",
"users"."name",
"users"."email",
"users"."isAdmin",
"users"."quotaUsageInBytes",
"users"."quotaSizeInBytes"
from from
"user_metadata" "users"
where where
"users"."id" = "user_metadata"."userId" "users"."id" = "api_keys"."userId"
) as "metadata" and "users"."deletedAt" is null
from ) as obj
"users" ) as "user"
where from
"users"."id" = "api_keys"."userId" "api_keys"
and "users"."deletedAt" is null
) as "user" on true
where where
"api_keys"."key" = $1 "api_keys"."key" = $1
+6
View File
@@ -159,6 +159,9 @@ where
"ownerId" = $1::uuid "ownerId" = $1::uuid
and "deviceId" = $2 and "deviceId" = $2
and "isVisible" = $3 and "isVisible" = $3
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
and "deletedAt" is null and "deletedAt" is null
-- AssetRepository.getLivePhotoCount -- AssetRepository.getLivePhotoCount
@@ -260,6 +263,9 @@ with
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."isVisible" = $2 and "assets"."isVisible" = $2
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
) )
select select
"timeBucket", "timeBucket",
+12
View File
@@ -13,6 +13,9 @@ where
and "assets"."isFavorite" = $4 and "assets"."isFavorite" = $4
and "assets"."isArchived" = $5 and "assets"."isArchived" = $5
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
order by order by
"assets"."fileCreatedAt" desc "assets"."fileCreatedAt" desc
limit limit
@@ -34,6 +37,9 @@ offset
and "assets"."isFavorite" = $4 and "assets"."isFavorite" = $4
and "assets"."isArchived" = $5 and "assets"."isArchived" = $5
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
and "assets"."id" < $6 and "assets"."id" < $6
order by order by
random() random()
@@ -54,6 +60,9 @@ union all
and "assets"."isFavorite" = $11 and "assets"."isFavorite" = $11
and "assets"."isArchived" = $12 and "assets"."isArchived" = $12
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
and "assets"."id" > $13 and "assets"."id" > $13
order by order by
random() random()
@@ -77,6 +86,9 @@ where
and "assets"."isFavorite" = $4 and "assets"."isFavorite" = $4
and "assets"."isArchived" = $5 and "assets"."isArchived" = $5
and "assets"."deletedAt" is null and "assets"."deletedAt" is null
and "assets"."fileCreatedAt" is not null
and "assets"."fileModifiedAt" is not null
and "assets"."localDateTime" is not null
order by order by
smart_search.embedding <=> $6 smart_search.embedding <=> $6
limit limit
+18 -30
View File
@@ -10,41 +10,29 @@ where
-- SessionRepository.getByToken -- SessionRepository.getByToken
select select
"sessions".*, "sessions"."id",
to_json("user") as "user" "sessions"."updatedAt",
from (
"sessions"
inner join lateral (
select select
"id", to_json(obj)
"email", from
"createdAt",
"profileImagePath",
"isAdmin",
"shouldChangePassword",
"deletedAt",
"oauthId",
"updatedAt",
"storageLabel",
"name",
"quotaSizeInBytes",
"quotaUsageInBytes",
"status",
"profileChangedAt",
( (
select select
array_agg("user_metadata") as "metadata" "users"."id",
"users"."name",
"users"."email",
"users"."isAdmin",
"users"."quotaUsageInBytes",
"users"."quotaSizeInBytes"
from from
"user_metadata" "users"
where where
"users"."id" = "user_metadata"."userId" "users"."id" = "sessions"."userId"
) as "metadata" and "users"."deletedAt" is null
from ) as obj
"users" ) as "user"
where from
"users"."id" = "sessions"."userId" "sessions"
and "users"."deletedAt" is null
) as "user" on true
where where
"sessions"."token" = $1 "sessions"."token" = $1
+11 -13
View File
@@ -153,12 +153,19 @@ where
"shared_links"."type" = $2 "shared_links"."type" = $2
or "album"."id" is not null or "album"."id" is not null
) )
and "shared_links"."albumId" = $3
order by order by
"shared_links"."createdAt" desc "shared_links"."createdAt" desc
-- SharedLinkRepository.getByKey -- SharedLinkRepository.getByKey
select select
"shared_links".*, "shared_links"."id",
"shared_links"."userId",
"shared_links"."expiresAt",
"shared_links"."showExif",
"shared_links"."allowUpload",
"shared_links"."allowDownload",
"shared_links"."password",
( (
select select
to_json(obj) to_json(obj)
@@ -166,20 +173,11 @@ select
( (
select select
"users"."id", "users"."id",
"users"."email",
"users"."createdAt",
"users"."profileImagePath",
"users"."isAdmin",
"users"."shouldChangePassword",
"users"."deletedAt",
"users"."oauthId",
"users"."updatedAt",
"users"."storageLabel",
"users"."name", "users"."name",
"users"."quotaSizeInBytes", "users"."email",
"users"."isAdmin",
"users"."quotaUsageInBytes", "users"."quotaUsageInBytes",
"users"."status", "users"."quotaSizeInBytes"
"users"."profileChangedAt"
from from
"users" "users"
where where
+6
View File
@@ -10,6 +10,9 @@ where
and "isVisible" = $3 and "isVisible" = $3
and "isArchived" = $4 and "isArchived" = $4
and "deletedAt" is null and "deletedAt" is null
and "fileModifiedAt" is not null
and "fileModifiedAt" is not null
and "localDateTime" is not null
-- ViewRepository.getAssetsByOriginalPath -- ViewRepository.getAssetsByOriginalPath
select select
@@ -23,6 +26,9 @@ where
and "isVisible" = $2 and "isVisible" = $2
and "isArchived" = $3 and "isArchived" = $3
and "deletedAt" is null and "deletedAt" is null
and "fileModifiedAt" is not null
and "fileModifiedAt" is not null
and "localDateTime" is not null
and "originalPath" like $4 and "originalPath" like $4
and "originalPath" not like $5 and "originalPath" not like $5
order by order by
@@ -65,6 +65,9 @@ export class ActivityRepository {
.where('activity.albumId', '=', albumId) .where('activity.albumId', '=', albumId)
.where('activity.isLiked', '=', false) .where('activity.isLiked', '=', false)
.where('assets.deletedAt', 'is', null) .where('assets.deletedAt', 'is', null)
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null)
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
return count as number; return count as number;
+12 -2
View File
@@ -6,7 +6,17 @@ import { Albums, DB } from 'src/db';
import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { AlbumUserCreateDto } from 'src/dtos/album.dto'; import { AlbumUserCreateDto } from 'src/dtos/album.dto';
import { AlbumEntity } from 'src/entities/album.entity'; import { AlbumEntity } from 'src/entities/album.entity';
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
export interface AlbumAssetCount {
albumId: string;
assetCount: number;
startDate: Date | null;
endDate: Date | null;
}
export interface AlbumInfoOptions {
withAssets: boolean;
}
const userColumns = [ const userColumns = [
'id', 'id',
@@ -71,7 +81,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
}; };
@Injectable() @Injectable()
export class AlbumRepository implements IAlbumRepository { export class AlbumRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] }) @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] })
+10 -24
View File
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely'; import { Insertable, Kysely, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { ApiKeys, DB } from 'src/db'; import { ApiKeys, DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { asUuid } from 'src/utils/database'; import { asUuid } from 'src/utils/database';
const columns = ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'] as const;
@Injectable() @Injectable()
export class ApiKeyRepository { export class ApiKeyRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@@ -33,29 +33,15 @@ export class ApiKeyRepository {
getKey(hashedToken: string) { getKey(hashedToken: string) {
return this.db return this.db
.selectFrom('api_keys') .selectFrom('api_keys')
.innerJoinLateral( .select((eb) => [
(eb) => ...columns.authApiKey,
jsonObjectFrom(
eb eb
.selectFrom('users') .selectFrom('users')
.selectAll('users') .select(columns.authUser)
.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') .whereRef('users.id', '=', 'api_keys.userId')
.where('users.deletedAt', 'is', null) .where('users.deletedAt', 'is', null),
.as('user'), ).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) .where('api_keys.key', '=', hashedToken)
.executeTakeFirst(); .executeTakeFirst();
@@ -65,7 +51,7 @@ export class ApiKeyRepository {
getById(userId: string, id: string) { getById(userId: string, id: string) {
return this.db return this.db
.selectFrom('api_keys') .selectFrom('api_keys')
.select(columns) .select(columns.apiKey)
.where('id', '=', asUuid(id)) .where('id', '=', asUuid(id))
.where('userId', '=', userId) .where('userId', '=', userId)
.executeTakeFirst(); .executeTakeFirst();
@@ -75,7 +61,7 @@ export class ApiKeyRepository {
getByUserId(userId: string) { getByUserId(userId: string) {
return this.db return this.db
.selectFrom('api_keys') .selectFrom('api_keys')
.select(columns) .select(columns.apiKey)
.where('userId', '=', userId) .where('userId', '=', userId)
.orderBy('createdAt', 'desc') .orderBy('createdAt', 'desc')
.execute(); .execute();
+148 -27
View File
@@ -7,6 +7,7 @@ import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { import {
AssetEntity, AssetEntity,
AssetEntityPlaceholder,
hasPeople, hasPeople,
searchAssetBuilder, searchAssetBuilder,
truncatedDate, truncatedDate,
@@ -21,34 +22,138 @@ import {
withTagId, withTagId,
withTags, withTags,
} from 'src/entities/asset.entity'; } from 'src/entities/asset.entity';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum'; import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum';
import {
AssetDeltaSyncOptions,
AssetExploreFieldOptions,
AssetFullSyncOptions,
AssetGetByChecksumOptions,
AssetStats,
AssetStatsOptions,
AssetUpdateDuplicateOptions,
DayOfYearAssets,
DuplicateGroup,
GetByIdsRelations,
IAssetRepository,
LivePhotoSearchOptions,
MonthDay,
TimeBucketItem,
TimeBucketOptions,
TimeBucketSize,
WithProperty,
WithoutProperty,
} from 'src/interfaces/asset.interface';
import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/interfaces/search.interface';
import { MapMarker, MapMarkerSearchOptions } from 'src/repositories/map.repository'; import { MapMarker, MapMarkerSearchOptions } from 'src/repositories/map.repository';
import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/repositories/search.repository';
import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database'; import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database';
import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination'; import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination';
export type AssetStats = Record<AssetType, number>;
export interface AssetStatsOptions {
isFavorite?: boolean;
isArchived?: boolean;
isTrashed?: boolean;
}
export interface LivePhotoSearchOptions {
ownerId: string;
libraryId?: string | null;
livePhotoCID: string;
otherAssetId: string;
type: AssetType;
}
export enum WithoutProperty {
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded-video',
EXIF = 'exif',
SMART_SEARCH = 'smart-search',
DUPLICATE = 'duplicate',
FACES = 'faces',
SIDECAR = 'sidecar',
}
export enum WithProperty {
SIDECAR = 'sidecar',
}
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
export interface AssetBuilderOptions {
isArchived?: boolean;
isFavorite?: boolean;
isTrashed?: boolean;
isDuplicate?: boolean;
albumId?: string;
tagId?: string;
personId?: string;
userIds?: string[];
withStacked?: boolean;
exifInfo?: boolean;
status?: AssetStatus;
assetType?: AssetType;
}
export interface TimeBucketOptions extends AssetBuilderOptions {
size: TimeBucketSize;
order?: AssetOrder;
}
export interface TimeBucketItem {
timeBucket: string;
count: number;
}
export interface MonthDay {
day: number;
month: number;
}
export interface AssetExploreFieldOptions {
maxFields: number;
minAssetsPerField: number;
}
export interface AssetFullSyncOptions {
ownerId: string;
lastId?: string;
updatedUntil: Date;
limit: number;
}
export interface AssetDeltaSyncOptions {
userIds: string[];
updatedAfter: Date;
limit: number;
}
export interface AssetUpdateDuplicateOptions {
targetDuplicateId: string | null;
assetIds: string[];
duplicateIds: string[];
}
export interface UpsertFileOptions {
assetId: string;
type: AssetFileType;
path: string;
}
export interface AssetGetByChecksumOptions {
ownerId: string;
checksum: Buffer;
libraryId?: string;
}
export type AssetPathEntity = Pick<AssetEntity, 'id' | 'originalPath' | 'isOffline'>;
export interface GetByIdsRelations {
exifInfo?: boolean;
faces?: { person?: boolean };
files?: boolean;
library?: boolean;
owner?: boolean;
smartSearch?: boolean;
stack?: { assets?: boolean };
tags?: boolean;
}
export interface DuplicateGroup {
duplicateId: string;
assets: AssetEntity[];
}
export interface DayOfYearAssets {
yearsAgo: number;
assets: AssetEntity[];
}
@Injectable() @Injectable()
export class AssetRepository implements IAssetRepository { export class AssetRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
async upsertExif(exif: Insertable<Exif>): Promise<void> { async upsertExif(exif: Insertable<Exif>): Promise<void> {
@@ -79,8 +184,12 @@ export class AssetRepository implements IAssetRepository {
.execute(); .execute();
} }
create(asset: Insertable<Assets>): Promise<AssetEntity> { create(asset: Insertable<Assets>): Promise<AssetEntityPlaceholder> {
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirst() as any as Promise<AssetEntity>; return this.db
.insertInto('assets')
.values(asset)
.returningAll()
.executeTakeFirst() as any as Promise<AssetEntityPlaceholder>;
} }
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] }) @GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
@@ -291,6 +400,9 @@ export class AssetRepository implements IAssetRepository {
.where('ownerId', '=', asUuid(ownerId)) .where('ownerId', '=', asUuid(ownerId))
.where('deviceId', '=', deviceId) .where('deviceId', '=', deviceId)
.where('isVisible', '=', true) .where('isVisible', '=', true)
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null)
.where('deletedAt', 'is', null) .where('deletedAt', 'is', null)
.execute(); .execute();
@@ -458,7 +570,10 @@ export class AssetRepository implements IAssetRepository {
.where('job_status.duplicatesDetectedAt', 'is', null) .where('job_status.duplicatesDetectedAt', 'is', null)
.where('job_status.previewAt', 'is not', null) .where('job_status.previewAt', 'is not', null)
.where((eb) => eb.exists(eb.selectFrom('smart_search').where('assetId', '=', eb.ref('assets.id')))) .where((eb) => eb.exists(eb.selectFrom('smart_search').where('assetId', '=', eb.ref('assets.id'))))
.where('assets.isVisible', '=', true), .where('assets.isVisible', '=', true)
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null),
) )
.$if(property === WithoutProperty.ENCODED_VIDEO, (qb) => .$if(property === WithoutProperty.ENCODED_VIDEO, (qb) =>
qb qb
@@ -524,7 +639,7 @@ export class AssetRepository implements IAssetRepository {
.executeTakeFirst() as Promise<AssetEntity | undefined>; .executeTakeFirst() as Promise<AssetEntity | undefined>;
} }
getMapMarkers(ownerIds: string[], options: MapMarkerSearchOptions = {}): Promise<MapMarker[]> { private getMapMarkers(ownerIds: string[], options: MapMarkerSearchOptions = {}): Promise<MapMarker[]> {
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options; const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
return this.db return this.db
@@ -552,6 +667,9 @@ export class AssetRepository implements IAssetRepository {
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO)) .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
.select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER)) .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
.where('ownerId', '=', asUuid(ownerId)) .where('ownerId', '=', asUuid(ownerId))
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null)
.where('isVisible', '=', true) .where('isVisible', '=', true)
.$if(isArchived !== undefined, (qb) => qb.where('isArchived', '=', isArchived!)) .$if(isArchived !== undefined, (qb) => qb.where('isArchived', '=', isArchived!))
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!)) .$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
@@ -584,6 +702,9 @@ export class AssetRepository implements IAssetRepository {
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.where('assets.isVisible', '=', true) .where('assets.isVisible', '=', true)
.where('assets.fileCreatedAt', 'is not', null)
.where('assets.fileModifiedAt', 'is not', null)
.where('assets.localDateTime', 'is not', null)
.$if(!!options.albumId, (qb) => .$if(!!options.albumId, (qb) =>
qb qb
.innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId') .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
+10 -3
View File
@@ -13,9 +13,16 @@ import { Notice } from 'postgres';
import { citiesFile, excludePaths, IWorker } from 'src/constants'; import { citiesFile, excludePaths, IWorker } from 'src/constants';
import { Telemetry } from 'src/decorators'; import { Telemetry } from 'src/decorators';
import { EnvDto } from 'src/dtos/env.dto'; import { EnvDto } from 'src/dtos/env.dto';
import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; import {
import { DatabaseConnectionParams, DatabaseExtension, VectorExtension } from 'src/interfaces/database.interface'; DatabaseExtension,
import { QueueName } from 'src/interfaces/job.interface'; ImmichEnvironment,
ImmichHeader,
ImmichTelemetry,
ImmichWorker,
LogLevel,
QueueName,
} from 'src/enum';
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
import { setDifference } from 'src/utils/set'; import { setDifference } from 'src/utils/set';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
+2 -3
View File
@@ -2,11 +2,10 @@ import { Injectable } from '@nestjs/common';
import { compareSync, hash } from 'bcrypt'; import { compareSync, hash } from 'bcrypt';
import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto'; import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto';
import { createReadStream } from 'node:fs'; import { createReadStream } from 'node:fs';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
@Injectable() @Injectable()
export class CryptoRepository implements ICryptoRepository { export class CryptoRepository {
randomUUID() { randomUUID(): string {
return randomUUID(); return randomUUID();
} }
+4 -12
View File
@@ -4,26 +4,18 @@ import AsyncLock from 'async-lock';
import { Kysely, sql } from 'kysely'; import { Kysely, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import semver from 'semver'; import semver from 'semver';
import { POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants'; import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants';
import { DB } from 'src/db'; import { DB } from 'src/db';
import { import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum';
DatabaseExtension,
DatabaseLock,
EXTENSION_NAMES,
ExtensionVersion,
IDatabaseRepository,
VectorExtension,
VectorIndex,
VectorUpdateResult,
} from 'src/interfaces/database.interface';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
import { UPSERT_COLUMNS } from 'src/utils/database'; import { UPSERT_COLUMNS } from 'src/utils/database';
import { isValidInteger } from 'src/validation'; import { isValidInteger } from 'src/validation';
import { DataSource, EntityManager, EntityMetadata, QueryRunner } from 'typeorm'; import { DataSource, EntityManager, EntityMetadata, QueryRunner } from 'typeorm';
@Injectable() @Injectable()
export class DatabaseRepository implements IDatabaseRepository { export class DatabaseRepository {
private vectorExtension: VectorExtension; private vectorExtension: VectorExtension;
private readonly asyncLock = new AsyncLock(); private readonly asyncLock = new AsyncLock();
+105 -18
View File
@@ -10,21 +10,15 @@ import {
import { ClassConstructor } from 'class-transformer'; import { ClassConstructor } from 'class-transformer';
import _ from 'lodash'; import _ from 'lodash';
import { Server, Socket } from 'socket.io'; import { Server, Socket } from 'socket.io';
import { SystemConfig } from 'src/config';
import { EventConfig } from 'src/decorators'; import { EventConfig } from 'src/decorators';
import { ImmichWorker, MetadataKey } from 'src/enum'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { import { AuthDto } from 'src/dtos/auth.dto';
ArgsOf, import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
ClientEventMap, import { ImmichWorker, MetadataKey, QueueName } from 'src/enum';
EmitEvent,
EmitHandler,
EventItem,
IEventRepository,
serverEvents,
ServerEvents,
} from 'src/interfaces/event.interface';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { AuthService } from 'src/services/auth.service'; import { JobItem } from 'src/types';
import { handlePromiseError } from 'src/utils/misc'; import { handlePromiseError } from 'src/utils/misc';
type EmitHandlers = Partial<{ [T in EmitEvent]: Array<EventItem<T>> }>; type EmitHandlers = Partial<{ [T in EmitEvent]: Array<EventItem<T>> }>;
@@ -37,14 +31,99 @@ type Item<T extends EmitEvent> = {
label: string; label: string;
}; };
type EventMap = {
// app events
'app.bootstrap': [];
'app.shutdown': [];
'config.init': [{ newConfig: SystemConfig }];
// config events
'config.update': [
{
newConfig: SystemConfig;
oldConfig: SystemConfig;
},
];
'config.validate': [{ newConfig: SystemConfig; oldConfig: SystemConfig }];
// album events
'album.update': [{ id: string; recipientIds: string[] }];
'album.invite': [{ id: string; userId: string }];
// asset events
'asset.tag': [{ assetId: string }];
'asset.untag': [{ assetId: string }];
'asset.hide': [{ assetId: string; userId: string }];
'asset.show': [{ assetId: string; userId: string }];
'asset.trash': [{ assetId: string; userId: string }];
'asset.delete': [{ assetId: string; userId: string }];
// asset bulk events
'assets.trash': [{ assetIds: string[]; userId: string }];
'assets.delete': [{ assetIds: string[]; userId: string }];
'assets.restore': [{ assetIds: string[]; userId: string }];
'job.start': [QueueName, JobItem];
// session events
'session.delete': [{ sessionId: string }];
// stack events
'stack.create': [{ stackId: string; userId: string }];
'stack.update': [{ stackId: string; userId: string }];
'stack.delete': [{ stackId: string; userId: string }];
// stack bulk events
'stacks.delete': [{ stackIds: string[]; userId: string }];
// user events
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
// websocket events
'websocket.connect': [{ userId: string }];
};
export const serverEvents = ['config.update'] as const;
export type ServerEvents = (typeof serverEvents)[number];
export type EmitEvent = keyof EventMap;
export type EmitHandler<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<void> | void;
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
export type ArgsOf<T extends EmitEvent> = EventMap[T];
export interface ClientEventMap {
on_upload_success: [AssetResponseDto];
on_user_delete: [string];
on_asset_delete: [string];
on_asset_trash: [string[]];
on_asset_update: [AssetResponseDto];
on_asset_hidden: [string];
on_asset_restore: [string[]];
on_asset_stack_update: string[];
on_person_thumbnail: [string];
on_server_version: [ServerVersionResponseDto];
on_config_update: [];
on_new_release: [ReleaseNotification];
on_session_delete: [string];
}
export type EventItem<T extends EmitEvent> = {
event: T;
handler: EmitHandler<T>;
server: boolean;
};
export type AuthFn = (client: Socket) => Promise<AuthDto>;
@WebSocketGateway({ @WebSocketGateway({
cors: true, cors: true,
path: '/api/socket.io', path: '/api/socket.io',
transports: ['websocket'], transports: ['websocket'],
}) })
@Injectable() @Injectable()
export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, IEventRepository { export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit {
private emitHandlers: EmitHandlers = {}; private emitHandlers: EmitHandlers = {};
private authFn?: AuthFn;
@WebSocketServer() @WebSocketServer()
private server?: Server; private server?: Server;
@@ -122,11 +201,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
async handleConnection(client: Socket) { async handleConnection(client: Socket) {
try { try {
this.logger.log(`Websocket Connect: ${client.id}`); this.logger.log(`Websocket Connect: ${client.id}`);
const auth = await this.moduleRef.get(AuthService).authenticate({ const auth = await this.authenticate(client);
headers: client.request.headers,
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' },
});
await client.join(auth.user.id); await client.join(auth.user.id);
if (auth.session) { if (auth.session) {
await client.join(auth.session.id); await client.join(auth.session.id);
@@ -182,4 +257,16 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
this.logger.debug(`Server event: ${event} (send)`); this.logger.debug(`Server event: ${event} (send)`);
this.server?.serverSideEmit(event, ...args); this.server?.serverSideEmit(event, ...args);
} }
setAuthFn(fn: (client: Socket) => Promise<AuthDto>) {
this.authFn = fn;
}
private async authenticate(client: Socket) {
if (!this.authFn) {
throw new Error('Auth function not set');
}
return this.authFn(client);
}
} }
+17 -37
View File
@@ -1,20 +1,3 @@
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.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 { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IPersonRepository } from 'src/interfaces/person.interface';
import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { IStackRepository } from 'src/interfaces/stack.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ITagRepository } from 'src/interfaces/tag.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
import { ActivityRepository } from 'src/repositories/activity.repository'; import { ActivityRepository } from 'src/repositories/activity.repository';
import { AlbumUserRepository } from 'src/repositories/album-user.repository'; import { AlbumUserRepository } from 'src/repositories/album-user.repository';
@@ -58,44 +41,41 @@ import { ViewRepository } from 'src/repositories/view-repository';
export const repositories = [ export const repositories = [
AccessRepository, AccessRepository,
ActivityRepository, ActivityRepository,
AlbumRepository,
AlbumUserRepository, AlbumUserRepository,
AuditRepository, AuditRepository,
ApiKeyRepository, ApiKeyRepository,
AssetRepository,
ConfigRepository, ConfigRepository,
CronRepository, CronRepository,
CryptoRepository,
DatabaseRepository,
EventRepository,
JobRepository,
LibraryRepository,
LoggingRepository, LoggingRepository,
MachineLearningRepository,
MapRepository, MapRepository,
MediaRepository, MediaRepository,
MemoryRepository, MemoryRepository,
MetadataRepository, MetadataRepository,
MoveRepository,
NotificationRepository, NotificationRepository,
OAuthRepository, OAuthRepository,
PartnerRepository,
PersonRepository,
ProcessRepository, ProcessRepository,
SearchRepository,
SessionRepository, SessionRepository,
ServerInfoRepository, ServerInfoRepository,
SharedLinkRepository,
StackRepository,
StorageRepository,
SystemMetadataRepository, SystemMetadataRepository,
TagRepository,
TelemetryRepository, TelemetryRepository,
TrashRepository, TrashRepository,
UserRepository,
ViewRepository, ViewRepository,
VersionHistoryRepository, VersionHistoryRepository,
]; ];
export const providers = [
{ provide: IAlbumRepository, useClass: AlbumRepository },
{ provide: IAssetRepository, useClass: AssetRepository },
{ provide: ICryptoRepository, useClass: CryptoRepository },
{ provide: IDatabaseRepository, useClass: DatabaseRepository },
{ provide: IEventRepository, useClass: EventRepository },
{ provide: IJobRepository, useClass: JobRepository },
{ provide: ILibraryRepository, useClass: LibraryRepository },
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
{ provide: IMoveRepository, useClass: MoveRepository },
{ provide: IPartnerRepository, useClass: PartnerRepository },
{ provide: IPersonRepository, useClass: PersonRepository },
{ provide: ISearchRepository, useClass: SearchRepository },
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
{ provide: IStackRepository, useClass: StackRepository },
{ provide: IStorageRepository, useClass: StorageRepository },
{ provide: ITagRepository, useClass: TagRepository },
{ provide: IUserRepository, useClass: UserRepository },
];
+6 -17
View File
@@ -1,26 +1,15 @@
import { getQueueToken } from '@nestjs/bullmq'; import { getQueueToken } from '@nestjs/bullmq';
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core'; import { ModuleRef, Reflector } from '@nestjs/core';
import { JobsOptions, Queue, Worker } from 'bullmq'; import { JobsOptions, Queue, Worker } from 'bullmq';
import { ClassConstructor } from 'class-transformer'; import { ClassConstructor } from 'class-transformer';
import { setTimeout } from 'node:timers/promises'; import { setTimeout } from 'node:timers/promises';
import { JobConfig } from 'src/decorators'; import { JobConfig } from 'src/decorators';
import { MetadataKey } from 'src/enum'; import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueName } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface';
import {
IEntityJob,
IJobRepository,
JobCounts,
JobItem,
JobName,
JobOf,
JobStatus,
QueueCleanType,
QueueName,
QueueStatus,
} from 'src/interfaces/job.interface';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { IEntityJob, JobCounts, JobItem, JobOf, QueueStatus } from 'src/types';
import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc'; import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc';
type JobMapItem = { type JobMapItem = {
@@ -31,14 +20,14 @@ type JobMapItem = {
}; };
@Injectable() @Injectable()
export class JobRepository implements IJobRepository { export class JobRepository {
private workers: Partial<Record<QueueName, Worker>> = {}; private workers: Partial<Record<QueueName, Worker>> = {};
private handlers: Partial<Record<JobName, JobMapItem>> = {}; private handlers: Partial<Record<JobName, JobMapItem>> = {};
constructor( constructor(
private moduleRef: ModuleRef, private moduleRef: ModuleRef,
private configRepository: ConfigRepository, private configRepository: ConfigRepository,
@Inject(IEventRepository) private eventRepository: IEventRepository, private eventRepository: EventRepository,
private logger: LoggingRepository, private logger: LoggingRepository,
) { ) {
this.logger.setContext(JobRepository.name); this.logger.setContext(JobRepository.name);
@@ -7,7 +7,6 @@ import { DummyValue, GenerateSql } from 'src/decorators';
import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
import { LibraryEntity } from 'src/entities/library.entity'; import { LibraryEntity } from 'src/entities/library.entity';
import { AssetType } from 'src/enum'; import { AssetType } from 'src/enum';
import { ILibraryRepository } from 'src/interfaces/library.interface';
const userColumns = [ const userColumns = [
'users.id', 'users.id',
@@ -34,7 +33,7 @@ const withOwner = (eb: ExpressionBuilder<DB, 'libraries'>) => {
}; };
@Injectable() @Injectable()
export class LibraryRepository implements ILibraryRepository { export class LibraryRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
@@ -1,14 +1,14 @@
import { ClsService } from 'nestjs-cls'; import { ClsService } from 'nestjs-cls';
import { ImmichWorker } from 'src/enum'; import { ImmichWorker } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { IConfigRepository } from 'src/types';
import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
describe(LoggingRepository.name, () => { describe(LoggingRepository.name, () => {
let sut: LoggingRepository; let sut: LoggingRepository;
let configMock: Mocked<IConfigRepository>; let configMock: Mocked<ConfigRepository>;
let clsMock: Mocked<ClsService>; let clsMock: Mocked<ClsService>;
beforeEach(() => { beforeEach(() => {
@@ -1,21 +1,60 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import { CLIPConfig } from 'src/dtos/model-config.dto'; import { CLIPConfig } from 'src/dtos/model-config.dto';
import {
ClipTextualResponse,
ClipVisualResponse,
FaceDetectionOptions,
FacialRecognitionResponse,
IMachineLearningRepository,
MachineLearningRequest,
ModelPayload,
ModelTask,
ModelType,
} from 'src/interfaces/machine-learning.interface';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
export interface BoundingBox {
x1: number;
y1: number;
x2: number;
y2: number;
}
export enum ModelTask {
FACIAL_RECOGNITION = 'facial-recognition',
SEARCH = 'clip',
}
export enum ModelType {
DETECTION = 'detection',
PIPELINE = 'pipeline',
RECOGNITION = 'recognition',
TEXTUAL = 'textual',
VISUAL = 'visual',
}
export type ModelPayload = { imagePath: string } | { text: string };
type ModelOptions = { modelName: string };
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]: string } & VisualResponse;
export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } };
export type ClipTextualResponse = { [ModelTask.SEARCH]: string };
export type FacialRecognitionRequest = {
[ModelTask.FACIAL_RECOGNITION]: {
[ModelType.DETECTION]: ModelOptions & { options: { minScore: number } };
[ModelType.RECOGNITION]: ModelOptions;
};
};
export interface Face {
boundingBox: BoundingBox;
embedding: string;
score: number;
}
export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse;
export type DetectedFaces = { faces: Face[] } & VisualResponse;
export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest;
@Injectable() @Injectable()
export class MachineLearningRepository implements IMachineLearningRepository { export class MachineLearningRepository {
constructor(private logger: LoggingRepository) { constructor(private logger: LoggingRepository) {
this.logger.setContext(MachineLearningRepository.name); this.logger.setContext(MachineLearningRepository.name);
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { DB, Memories } from 'src/db'; import { DB, Memories } from 'src/db';
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { IBulkAsset } from 'src/utils/asset.util'; import { IBulkAsset } from 'src/types';
@Injectable() @Injectable()
export class MemoryRepository implements IBulkAsset { export class MemoryRepository implements IBulkAsset {
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
import geotz from 'geo-tz'; import geotz from 'geo-tz';
import { LogLevel } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
interface ExifDuration { interface ExifDuration {
@@ -101,6 +102,9 @@ export class MetadataRepository {
} }
async writeTags(path: string, tags: Partial<Tags>): Promise<void> { async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
if (this.logger.isLevelEnabled(LogLevel.VERBOSE)) {
this.logger.verbose(`Writing tags ${JSON.stringify(tags)} to ${path}`);
}
try { try {
await this.exiftool.write(path, tags); await this.exiftool.write(path, tags);
} catch (error) { } catch (error) {
+3 -2
View File
@@ -5,10 +5,11 @@ import { DB, MoveHistory } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { MoveEntity } from 'src/entities/move.entity'; import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum'; import { PathType } from 'src/enum';
import { IMoveRepository } from 'src/interfaces/move.interface';
export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
@Injectable() @Injectable()
export class MoveRepository implements IMoveRepository { export class MoveRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
create(entity: Insertable<MoveHistory>): Promise<MoveEntity> { create(entity: Insertable<MoveHistory>): Promise<MoveEntity> {
@@ -1,17 +1,16 @@
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository'; import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository';
import { ILoggingRepository } from 'src/types'; import { ILoggingRepository, newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
describe(NotificationRepository.name, () => { describe(NotificationRepository.name, () => {
let sut: NotificationRepository; let sut: NotificationRepository;
let loggerMock: Mocked<ILoggingRepository>; let loggerMock: Mocked<LoggingRepository>;
beforeEach(() => { beforeEach(() => {
loggerMock = newLoggingRepositoryMock(); loggerMock = newLoggingRepositoryMock() as ILoggingRepository as Mocked<LoggingRepository>;
sut = new NotificationRepository(loggerMock as ILoggingRepository as LoggingRepository); sut = new NotificationRepository(loggerMock as LoggingRepository);
}); });
describe('renderEmail', () => { describe('renderEmail', () => {
@@ -7,12 +7,7 @@ import { AlbumUpdateEmail } from 'src/emails/album-update.email';
import { TestEmail } from 'src/emails/test.email'; import { TestEmail } from 'src/emails/test.email';
import { WelcomeEmail } from 'src/emails/welcome.email'; import { WelcomeEmail } from 'src/emails/welcome.email';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { EmailImageAttachment } from 'src/types';
export type EmailImageAttachment = {
filename: string;
path: string;
cid: string;
};
export type SendEmailOptions = { export type SendEmailOptions = {
from: string; from: string;
+11 -2
View File
@@ -5,7 +5,16 @@ import { InjectKysely } from 'nestjs-kysely';
import { DB, Partners, Users } from 'src/db'; import { DB, Partners, Users } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { PartnerEntity } from 'src/entities/partner.entity'; import { PartnerEntity } from 'src/entities/partner.entity';
import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface';
export interface PartnerIds {
sharedById: string;
sharedWithId: string;
}
export enum PartnerDirection {
SharedBy = 'shared-by',
SharedWith = 'shared-with',
}
const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
@@ -28,7 +37,7 @@ const withSharedWith = (eb: ExpressionBuilder<DB, 'partners'>) => {
}; };
@Injectable() @Injectable()
export class PartnerRepository implements IPartnerRepository { export class PartnerRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
+45 -15
View File
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ExpressionBuilder, Insertable, Kysely, sql } from 'kysely'; import { ExpressionBuilder, Insertable, Kysely, Selectable, sql } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { AssetFaces, DB, FaceSearch, Person } from 'src/db'; import { AssetFaces, DB, FaceSearch, Person } from 'src/db';
@@ -7,23 +7,53 @@ import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { PersonEntity } from 'src/entities/person.entity'; import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum'; import { SourceType } from 'src/enum';
import {
AssetFaceId,
DeleteFacesOptions,
IPersonRepository,
PeopleStatistics,
PersonNameResponse,
PersonNameSearchOptions,
PersonSearchOptions,
PersonStatistics,
SelectFaceOptions,
UnassignFacesOptions,
UpdateFacesData,
} from 'src/interfaces/person.interface';
import { mapUpsertColumns } from 'src/utils/database'; import { mapUpsertColumns } from 'src/utils/database';
import { Paginated, PaginationOptions } from 'src/utils/pagination'; import { Paginated, PaginationOptions } from 'src/utils/pagination';
import { FindOptionsRelations } from 'typeorm'; import { FindOptionsRelations } from 'typeorm';
export interface PersonSearchOptions {
minimumFaceCount: number;
withHidden: boolean;
closestFaceAssetId?: string;
}
export interface PersonNameSearchOptions {
withHidden?: boolean;
}
export interface PersonNameResponse {
id: string;
name: string;
}
export interface AssetFaceId {
assetId: string;
personId: string;
}
export interface UpdateFacesData {
oldPersonId?: string;
faceIds?: string[];
newPersonId: string;
}
export interface PersonStatistics {
assets: number;
}
export interface PeopleStatistics {
total: number;
hidden: number;
}
export interface DeleteFacesOptions {
sourceType: SourceType;
}
export type UnassignFacesOptions = DeleteFacesOptions;
export type SelectFaceOptions = (keyof Selectable<AssetFaces>)[];
const withPerson = (eb: ExpressionBuilder<DB, 'asset_faces'>) => { const withPerson = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
return jsonObjectFrom( return jsonObjectFrom(
eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'), eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'),
@@ -43,7 +73,7 @@ const withFaceSearch = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
}; };
@Injectable() @Injectable()
export class PersonRepository implements IPersonRepository { export class PersonRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] }) @GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
+190 -14
View File
@@ -6,26 +6,202 @@ import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetEntity, searchAssetBuilder } from 'src/entities/asset.entity'; import { AssetEntity, searchAssetBuilder } from 'src/entities/asset.entity';
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
import { AssetType } from 'src/enum'; import { AssetStatus, AssetType } from 'src/enum';
import {
AssetDuplicateSearch,
AssetSearchOptions,
FaceEmbeddingSearch,
GetCameraMakesOptions,
GetCameraModelsOptions,
GetCitiesOptions,
GetStatesOptions,
ISearchRepository,
SearchPaginationOptions,
SmartSearchOptions,
} from 'src/interfaces/search.interface';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { anyUuid, asUuid } from 'src/utils/database'; import { anyUuid, asUuid } from 'src/utils/database';
import { Paginated } from 'src/utils/pagination'; import { Paginated } from 'src/utils/pagination';
import { isValidInteger } from 'src/validation'; import { isValidInteger } from 'src/validation';
export interface SearchResult<T> {
/** total matches */
total: number;
/** collection size */
count: number;
/** current page */
page: number;
/** items for page */
items: T[];
/** score */
distances: number[];
facets: SearchFacet[];
}
export interface SearchFacet {
fieldName: string;
counts: Array<{
count: number;
value: string;
}>;
}
export type SearchExploreItemSet<T> = Array<{
value: string;
data: T;
}>;
export interface SearchExploreItem<T> {
fieldName: string;
items: SearchExploreItemSet<T>;
}
export interface SearchAssetIDOptions {
checksum?: Buffer;
deviceAssetId?: string;
id?: string;
}
export interface SearchUserIdOptions {
deviceId?: string;
libraryId?: string | null;
userIds?: string[];
}
export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions;
export interface SearchStatusOptions {
isArchived?: boolean;
isEncoded?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isVisible?: boolean;
isNotInAlbum?: boolean;
type?: AssetType;
status?: AssetStatus;
withArchived?: boolean;
withDeleted?: boolean;
}
export interface SearchOneToOneRelationOptions {
withExif?: boolean;
withStacked?: boolean;
}
export interface SearchRelationOptions extends SearchOneToOneRelationOptions {
withFaces?: boolean;
withPeople?: boolean;
}
export interface SearchDateOptions {
createdBefore?: Date;
createdAfter?: Date;
takenBefore?: Date;
takenAfter?: Date;
trashedBefore?: Date;
trashedAfter?: Date;
updatedBefore?: Date;
updatedAfter?: Date;
}
export interface SearchPathOptions {
encodedVideoPath?: string;
originalFileName?: string;
originalPath?: string;
previewPath?: string;
thumbnailPath?: string;
}
export interface SearchExifOptions {
city?: string | null;
country?: string | null;
lensModel?: string | null;
make?: string | null;
model?: string | null;
state?: string | null;
description?: string | null;
}
export interface SearchEmbeddingOptions {
embedding: string;
userIds: string[];
}
export interface SearchPeopleOptions {
personIds?: string[];
}
export interface SearchTagOptions {
tagIds?: string[];
}
export interface SearchOrderOptions {
orderDirection?: 'asc' | 'desc';
}
export interface SearchPaginationOptions {
page: number;
size: number;
}
type BaseAssetSearchOptions = SearchDateOptions &
SearchIdOptions &
SearchExifOptions &
SearchOrderOptions &
SearchPathOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions &
SearchTagOptions;
export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions;
export type AssetSearchOneToOneRelationOptions = BaseAssetSearchOptions & SearchOneToOneRelationOptions;
export type AssetSearchBuilderOptions = Omit<AssetSearchOptions, 'orderDirection'>;
export type SmartSearchOptions = SearchDateOptions &
SearchEmbeddingOptions &
SearchExifOptions &
SearchOneToOneRelationOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions &
SearchTagOptions;
export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
hasPerson?: boolean;
numResults: number;
maxDistance: number;
}
export interface AssetDuplicateSearch {
assetId: string;
embedding: string;
maxDistance: number;
type: AssetType;
userIds: string[];
}
export interface FaceSearchResult {
distance: number;
id: string;
personId: string | null;
}
export interface AssetDuplicateResult {
assetId: string;
duplicateId: string | null;
distance: number;
}
export interface GetStatesOptions {
country?: string;
}
export interface GetCitiesOptions extends GetStatesOptions {
state?: string;
}
export interface GetCameraModelsOptions {
make?: string;
}
export interface GetCameraMakesOptions {
model?: string;
}
@Injectable() @Injectable()
export class SearchRepository implements ISearchRepository { export class SearchRepository {
constructor( constructor(
private logger: LoggingRepository, private logger: LoggingRepository,
@InjectKysely() private db: Kysely<DB>, @InjectKysely() private db: Kysely<DB>,
+12 -3
View File
@@ -1,6 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely'; import { Insertable, Kysely, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Sessions } from 'src/db'; import { DB, Sessions } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { withUser } from 'src/entities/session.entity'; import { withUser } from 'src/entities/session.entity';
@@ -25,9 +27,16 @@ export class SessionRepository {
getByToken(token: string) { getByToken(token: string) {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.innerJoinLateral(withUser, (join) => join.onTrue()) .select((eb) => [
.selectAll('sessions') ...columns.authSession,
.select((eb) => eb.fn.toJson('user').as('user')) jsonObjectFrom(
eb
.selectFrom('users')
.select(columns.authUser)
.whereRef('users.id', '=', 'sessions.userId')
.where('users.deletedAt', 'is', null),
).as('user'),
])
.where('sessions.token', '=', token) .where('sessions.token', '=', token)
.executeTakeFirst(); .executeTakeFirst();
} }
@@ -3,14 +3,19 @@ import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { jsonObjectFrom } from 'kysely/helpers/postgres';
import _ from 'lodash'; import _ from 'lodash';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, SharedLinks } from 'src/db'; import { DB, SharedLinks } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { ISharedLinkRepository, SharedLinkSearchOptions } from 'src/interfaces/shared-link.interface';
export type SharedLinkSearchOptions = {
userId: string;
albumId?: string;
};
@Injectable() @Injectable()
export class SharedLinkRepository implements ISharedLinkRepository { export class SharedLinkRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
@@ -92,7 +97,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
.executeTakeFirst() as Promise<SharedLinkEntity | undefined>; .executeTakeFirst() as Promise<SharedLinkEntity | undefined>;
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] })
getAll({ userId, albumId }: SharedLinkSearchOptions): Promise<SharedLinkEntity[]> { getAll({ userId, albumId }: SharedLinkSearchOptions): Promise<SharedLinkEntity[]> {
return this.db return this.db
.selectFrom('shared_links') .selectFrom('shared_links')
@@ -156,39 +161,20 @@ export class SharedLinkRepository implements ISharedLinkRepository {
} }
@GenerateSql({ params: [DummyValue.BUFFER] }) @GenerateSql({ params: [DummyValue.BUFFER] })
async getByKey(key: Buffer): Promise<SharedLinkEntity | undefined> { async getByKey(key: Buffer) {
return this.db return this.db
.selectFrom('shared_links') .selectFrom('shared_links')
.selectAll('shared_links')
.where('shared_links.key', '=', key) .where('shared_links.key', '=', key)
.leftJoin('albums', 'albums.id', 'shared_links.albumId') .leftJoin('albums', 'albums.id', 'shared_links.albumId')
.where('albums.deletedAt', 'is', null) .where('albums.deletedAt', 'is', null)
.select((eb) => .select((eb) => [
...columns.authSharedLink,
jsonObjectFrom( jsonObjectFrom(
eb eb.selectFrom('users').select(columns.authUser).whereRef('users.id', '=', 'shared_links.userId'),
.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'), ).as('user'),
) ])
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('albums.id', 'is not', null)])) .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('albums.id', 'is not', null)]))
.executeTakeFirst() as Promise<SharedLinkEntity | undefined>; .executeTakeFirst();
} }
async create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity> { async create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity> {
+6 -2
View File
@@ -5,9 +5,13 @@ import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db'; import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { StackEntity } from 'src/entities/stack.entity'; import { StackEntity } from 'src/entities/stack.entity';
import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface';
import { asUuid } from 'src/utils/database'; import { asUuid } from 'src/utils/database';
export interface StackSearch {
ownerId: string;
primaryAssetId?: string;
}
const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false) => { const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false) => {
return jsonArrayFrom( return jsonArrayFrom(
eb eb
@@ -35,7 +39,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false)
}; };
@Injectable() @Injectable()
export class StackRepository implements IStackRepository { export class StackRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [{ ownerId: DummyValue.UUID }] }) @GenerateSql({ params: [{ ownerId: DummyValue.UUID }] })
@@ -2,8 +2,8 @@ import mockfs from 'mock-fs';
import { CrawlOptionsDto } from 'src/dtos/library.dto'; import { CrawlOptionsDto } from 'src/dtos/library.dto';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { StorageRepository } from 'src/repositories/storage.repository'; import { StorageRepository } from 'src/repositories/storage.repository';
import { ILoggingRepository } from 'src/types'; import { ILoggingRepository, newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock'; import { Mocked } from 'vitest';
interface Test { interface Test {
test: string; test: string;
@@ -182,11 +182,11 @@ const tests: Test[] = [
describe(StorageRepository.name, () => { describe(StorageRepository.name, () => {
let sut: StorageRepository; let sut: StorageRepository;
let logger: ILoggingRepository; let logger: Mocked<ILoggingRepository>;
beforeEach(() => { beforeEach(() => {
logger = newLoggingRepositoryMock(); logger = newLoggingRepositoryMock();
sut = new StorageRepository(logger as LoggingRepository); sut = new StorageRepository(logger as ILoggingRepository as LoggingRepository);
}); });
afterEach(() => { afterEach(() => {
+27 -9
View File
@@ -5,20 +5,38 @@ import { escapePath, glob, globStream } from 'fast-glob';
import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs'; import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { Writable } from 'node:stream'; import { Readable, Writable } from 'node:stream';
import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto';
import {
DiskUsage,
IStorageRepository,
ImmichReadStream,
ImmichZipStream,
WatchEvents,
} from 'src/interfaces/storage.interface';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { mimeTypes } from 'src/utils/mime-types'; import { mimeTypes } from 'src/utils/mime-types';
export interface WatchEvents {
onReady(): void;
onAdd(path: string): void;
onChange(path: string): void;
onUnlink(path: string): void;
onError(error: Error): void;
}
export interface ImmichReadStream {
stream: Readable;
type?: string;
length?: number;
}
export interface ImmichZipStream extends ImmichReadStream {
addFile: (inputPath: string, filename: string) => void;
finalize: () => Promise<void>;
}
export interface DiskUsage {
available: number;
free: number;
total: number;
}
@Injectable() @Injectable()
export class StorageRepository implements IStorageRepository { export class StorageRepository {
constructor(private logger: LoggingRepository) { constructor(private logger: LoggingRepository) {
this.logger.setContext(StorageRepository.name); this.logger.setContext(StorageRepository.name);
} }

Some files were not shown because too many files have changed in this diff Show More